API
The Essentials
Creating stores
Store
A Store is the main location for keeping both tabular data and keyed values.
Create a Store easily with the createStore function. From there, you can set and get data, add listeners for when the data changes, set schemas, and so on.
A Store has two facets. It can contain keyed Values, and independently, it can contain tabular Tables data. These two facets have similar APIs but can be used entirely independently: you can use only tables, only keyed Values, or both tables and keyed Values - all in a single Store.
Keyed values
The keyed value support is best thought of as a flat JavaScript object. The Store contains a number of Value objects, each with a unique ID, and which is a string, boolean, or number.
{ // Store
"value1": "one", // Value (string)
"value2": true, // Value (boolean)
"value3": 3, // Value (number)
...
}
In its default form, a Store has no sense of a structured schema for the Values. However, you can optionally specify a ValuesSchema for a Store, which then usefully constrains and defaults the Values you can use.
Tabular data
The tabular data exists in a simple hierarchical structure:
- The
Storecontains a number ofTableobjects. - Each
Tablecontains a number ofRowobjects. - Each
Rowcontains a number ofCellobjects.
A Cell is a string, boolean, or number value.
The members of each level of this hierarchy are identified with a unique Id (which is a string). In other words you can naively think of a Store as a three-level-deep JavaScript object, keyed with strings:
{ // Store
"table1": { // Table
"row1": { // Row
"cell1": "one", // Cell (string)
"cell2": true, // Cell (boolean)
"cell3": 3, // Cell (number)
...
},
...
},
...
}
Again, by default Store has no sense of a structured schema. As long as they are unique within their own parent, the Id keys can each be any string you want. However, as you can optionally specify a TablesSchema for the tabular data in a Store, which then usefully constrains the Table and Cell Ids (and Cell values) you can use.
Setting and getting data
Every part of the Store can be accessed with getter methods. When you retrieve data from the Store, you are receiving a copy - rather than a reference - of it. This means that manipulating the data in the Store must be performed with the equivalent setter and deleter methods.
To benefit from the reactive behavior of the Store, you can also subscribe to changes on any part of it with 'listeners'. Registering a listener returns a listener Id (that you can use later to remove it with the delListener method), and it will then be called every time there is a change within the part of the hierarchy you're listening to.
This table shows the main ways you can set, get, and listen to, different types of data in a Store:
There are two extra methods to manipulate Row objects. The addRow method is like the setRow method but automatically assigns it a new unique Id. And the setPartialRow method lets you update multiple Cell values in a Row without affecting the others. There is a similar setPartialValues method to do the same for the Values in a Store.
You can listen to attempts to write invalid data to a Value or Cell with the addInvalidValueListener method or addInvalidCellListener method.
The transaction method is used to wrap multiple changes to the Store so that the relevant listeners only fire once.
The setJson method and the getJson method allow you to work with a JSON-encoded representation of the entire Store, which is useful for persisting it.
Finally, the callListener method provides a way for you to manually provoke a listener to be called, even if the underlying data hasn't changed. This is useful when you are using mutator listeners to guarantee that data conforms to programmatic conditions, and those conditions change such that you need to update the Store in bulk.
Read more about setting and changing data in The Basics guides, and about listeners in the Listening to Stores guide.
Creating a schema
You can set a ValuesSchema and a TablesSchema with the setValuesSchema method and setTablesSchema method respectively. A TablesSchema constrains the Table Ids the Store can have, and the types of Cell data in each Table. Each Cell requires its type to be specified, and can also take a default value for when it's not specified.
You can also get a serialization of the schemas out of the Store with the getSchemaJson method, and remove the schemas altogether with the delValuesSchema method and delTablesSchema method.
Read more about schemas in the Using Schemas guide.
Convenience methods
There are a few additional helper methods to make it easier to work with a Store. There are methods for easily checking the existence of a Table, Row, or Cell, and iterators that let you act on the children of a common parent:
| Checking existence | Iterator | |
|---|---|---|
Value | hasValue | forEachValue |
Table | hasTable | forEachTable |
Row | hasRow | forEachRow |
Cell | hasCell | forEachCell |
Since v4.3.23, you can add listeners for the change of existence of part of a Store. For example, the addHasValueListener method lets you listen for a Value being added or removed.
Finally, the getListenerStats method describes the current state of the Store's listeners for debugging purposes.
Example
This example shows a very simple lifecycle of a Store: from creation, to adding and getting some data, and then registering and removing a listener.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getRow('pets', 'fido'));
// -> {species: 'dog'}
store.setCell('pets', 'fido', 'color', 'brown');
console.log(store.getCell('pets', 'fido', 'color'));
// -> 'brown'
const listenerId = store.addTableListener('pets', () => {
console.log('changed');
});
store.setCell('pets', 'fido', 'sold', false);
// -> 'changed'
store.delListener(listenerId);
See also
- The Basics guides
- Using Schemas guides
- Hello World demos
- Todo App demos
Since
v1.0.0
createMergeableStore
The createMergeableStore function creates a MergeableStore, and is the main entry point into the mergeable-store module.
createMergeableStore(
uniqueId?: string,
getNow?: GetNow,
): MergeableStore| Type | Description | |
|---|---|---|
uniqueId? | string | An optional unique |
getNow? | GetNow | An optional function that generates millisecond timestamps, since v6.1.0, defaulting to |
| returns | MergeableStore | A reference to the new |
There are two optional parameters which are only for testing and advanced usage.
The first is a uniqueId for the MergeableStore, used to distinguish conflicting changes made in the same millisecond by two different MergeableStore objects as its hash is added to the end of the HLC timestamps. Generally this can be omitted unless you have a need for deterministic HLCs, such as in a testing scenario. Otherwise, TinyBase will assign a unique Id to the Store at the time of creation.
Since v6.1.0, the second is a function that can be used to replace the way the timestamp is generated for HLCs (by default JavaScript's Date.now() method).
Examples
This example creates a MergeableStore.
import {createMergeableStore} from 'tinybase';
const store = createMergeableStore('store1');
console.log(store.getContent());
// -> [{}, {}]
console.log(store.getMergeableContent());
// -> [[{}, '', 0], [{}, '', 0]]
This example creates a MergeableStore with some initial data:
import {createMergeableStore} from 'tinybase';
const store = createMergeableStore('store1').setTables({
pets: {fido: {species: 'dog'}},
});
console.log(store.getContent());
// -> [{pets: {fido: {species: 'dog'}}}, {}]
console.log(store.getMergeableContent());
// ->
[
[
{
pets: [
{
fido: [
{species: ['dog', 'Nn1JUF-----FnHIC', 290599168]},
'',
2682656941,
],
},
'',
2102515304,
],
},
'',
3506229770,
],
[{}, '', 0],
];
This example creates a MergeableStore with some initial data and a TablesSchema:
import {createMergeableStore} from 'tinybase';
const store = createMergeableStore('store1')
.setTables({pets: {fido: {species: 'dog'}}})
.setTablesSchema({
pets: {
species: {type: 'string'},
sold: {type: 'boolean', default: false},
},
});
console.log(store.getContent());
// -> [{pets: {fido: {sold: false, species: 'dog'}}}, {}]
console.log(store.getMergeableContent());
// ->
[
[
{
pets: [
{
fido: [
{
sold: [false, 'Nn1JUF----2FnHIC', 2603026204],
species: ['dog', 'Nn1JUF----1FnHIC', 2817056260],
},
'',
2859424112,
],
},
'',
1640515891,
],
},
'',
2077041985,
],
[{}, '', 0],
];
Since
v5.0.0
createStore
The createStore function creates a Store, and is the main entry point into the store module.
createStore(): StoreSince (or perhaps because) it is the most important function in the whole module, it is trivially simple.
Examples
This example creates a Store.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getTables());
// -> {}
This example creates a Store with some initial data:
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
This example creates a Store with some initial data and a TablesSchema:
import {createStore} from 'tinybase';
const store = createStore()
.setTables({pets: {fido: {species: 'dog'}}})
.setTablesSchema({
pets: {
species: {type: 'string'},
sold: {type: 'boolean', default: false},
},
});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', sold: false}}}
See also
The Basics guides
Since
v1.0.0
Getting data
getRow
The getRow method returns an object containing the entire data of a single Row in a given Table.
getRow(
tableId: string,
rowId: string,
): RowNote that this returns a copy of, rather than a reference to the underlying data, so changes made to the returned object are not made to the Store itself.
Examples
This example retrieves the data in a single Row.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
console.log(store.getRow('pets', 'fido'));
// -> {species: 'dog'}
This example retrieves a Row that does not exist, returning an empty object.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getRow('pets', 'felix'));
// -> {}
Since
v1.0.0
getCell
The getCell method returns the value of a single Cell in a given Row, in a given Table.
getCell(
tableId: string,
rowId: string,
cellId: string,
): CellOrUndefined| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
| returns | CellOrUndefined | The value of the |
Examples
This example retrieves a single Cell.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
console.log(store.getCell('pets', 'fido', 'species'));
// -> 'dog'
This example retrieves a Cell that does not exist, returning undefined.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getCell('pets', 'fido', 'color'));
// -> undefined
Since
v1.0.0
getValue
The getValue method returns a single keyed Value in the Store.
getValue(valueId: string): ValueOrUndefined| Type | Description | |
|---|---|---|
valueId | string | |
| returns | ValueOrUndefined | The |
Examples
This example retrieves a single Value.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
console.log(store.getValue('employees'));
// -> 3
This example retrieves a Value that does not exist, returning undefined.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
console.log(store.getValue('website'));
// -> undefined
Since
v3.0.0
Setting data
setRow
The setRow method takes an object and sets the entire data of a single Row in the Store.
setRow(
tableId: string,
rowId: string,
row: Row,
): this| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
row | Row | The data of a single |
| returns | this | A reference to the |
This method will cause listeners to be called for any Table, Row, Cell, or Id changes resulting from it.
Any part of the provided object that is invalid (either according to the Row type, or because it does not match a TablesSchema associated with the Store), will be ignored silently.
Assuming that at least some of the provided Row object is valid, any data that was already present in the Store for that Row will be completely overwritten. If the object is completely invalid, no change will be made to the Store.
The method returns a reference to the Store so that subsequent operations can be chained in a fluent style.
Examples
This example sets the data of a single Row.
import {createStore} from 'tinybase';
const store = createStore().setRow('pets', 'fido', {species: 'dog'});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
This example attempts to set the data of an existing Store with partly invalid, and then completely invalid, Row objects.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.setRow('pets', 'fido', {color: 'brown', bug: []});
console.log(store.getTables());
// -> {pets: {fido: {color: 'brown'}}}
store.setRow('pets', 'fido', 42);
console.log(store.getTables());
// -> {pets: {fido: {color: 'brown'}}}
Since
v1.0.0
addRow
The addRow method takes an object and creates a new Row in the Store, returning the unique Id assigned to it.
addRow(
tableId: string,
row: Row,
reuseRowIds?: boolean,
): undefined | string| Type | Description | |
|---|---|---|
tableId | string | |
row | Row | The data of a single |
reuseRowIds? | boolean | Whether |
| returns | undefined | string | A reference to the |
This method will cause listeners to be called for any Table, Row, Cell, or Id changes resulting from it.
Any part of the provided object that is invalid (either according to the Row type, or because it does not match a TablesSchema associated with the Store), will be ignored silently.
Assuming that at least some of the provided Row object is valid, a new Row will be created. If the object is completely invalid, no change will be made to the Store and the method will return undefined.
You should not guarantee the form of the unique Id that is generated when a Row is added to the Table. However it is likely to be a string representation of an increasing integer.
The reuseRowIds parameter defaults to true, which means that if you delete a Row and then add another, the Id will be re-used - unless you delete the entire Table, in which case all Row Ids will reset. Otherwise, if you specify reuseRowIds to be false, then the Id will be a monotonically increasing string representation of an increasing integer, regardless of any you may have previously deleted.
Examples
This example adds a single Row.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.addRow('pets', {species: 'dog'}));
// -> '0'
console.log(store.getTables());
// -> {pets: {'0': {species: 'dog'}}}
This example attempts to add Rows to an existing Store with partly invalid, and then completely invalid, Row objects.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {'0': {species: 'dog'}}});
console.log(store.addRow('pets', {species: 'cat', bug: []}));
// -> '1'
console.log(store.getTables());
// -> {pets: {'0': {species: 'dog'}, '1': {species: 'cat'}}}
console.log(store.addRow('pets', 42));
// -> undefined
console.log(store.getTables());
// -> {pets: {'0': {species: 'dog'}, '1': {species: 'cat'}}}
Since
v1.0.0
setCell
The setCell method sets the value of a single Cell in the Store.
setCell(
tableId: string,
rowId: string,
cellId: string,
cell: Cell | MapCell,
): this| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
cell | Cell | MapCell | The value of the |
| returns | this | A reference to the |
This method will cause listeners to be called for any Table, Row, Cell, or Id changes resulting from it.
If the Cell value is invalid (either because of its type, or because it does not match a TablesSchema associated with the Store), will be ignored silently.
As well as string, number, or boolean Cell types, this method can also take a MapCell function that takes the current Cell value as a parameter and maps it. This is useful if you want to efficiently increment a value without fetching it first, for example.
The method returns a reference to the Store so that subsequent operations can be chained in a fluent style.
Examples
This example sets the value of a single Cell.
import {createStore} from 'tinybase';
const store = createStore().setCell('pets', 'fido', 'species', 'dog');
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
This example sets the data of a single Cell by mapping the existing value.
import {createStore} from 'tinybase';
const increment = (cell) => cell + 1;
const store = createStore().setTables({pets: {fido: {visits: 1}}});
store.setCell('pets', 'fido', 'visits', increment);
console.log(store.getCell('pets', 'fido', 'visits'));
// -> 2
This example attempts to set the data of an existing Store with an invalid Cell value.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.setCell('pets', 'fido', 'bug', []);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
Since
v1.0.0
setValue
The setValue method sets a single keyed Value in the Store.
setValue(
valueId: string,
value: Value | MapValue,
): thisThis method will cause listeners to be called for any Value, or Id changes resulting from it.
If the Value is invalid (either because of its type, or because it does not match a ValuesSchema associated with the Store), will be ignored silently.
As well as string, number, or boolean Value types, this method can also take a MapValue function that takes the current Value as a parameter and maps it. This is useful if you want to efficiently increment a value without fetching it first, for example.
The method returns a reference to the Store so that subsequent operations can be chained in a fluent style.
Examples
This example sets a single Value.
import {createStore} from 'tinybase';
const store = createStore().setValue('open', true);
console.log(store.getValues());
// -> {open: true}
This example sets the data of a single Value by mapping the existing Value.
import {createStore} from 'tinybase';
const increment = (value) => value + 1;
const store = createStore().setValues({employees: 3});
store.setValue('employees', increment);
console.log(store.getValue('employees'));
// -> 4
This example attempts to set an invalid Value.
import {createStore} from 'tinybase';
const store = createStore().setValues({employees: 3});
store.setValue('bug', []);
console.log(store.getValues());
// -> {employees: 3}
Since
v3.0.0
transaction
The transaction method takes a function that makes multiple mutations to the Store, buffering all calls to the relevant listeners until it completes.
transaction<Return>(
actions: () => Return,
doRollback?: DoRollback,
): Return| Type | Description | |
|---|---|---|
actions | () => Return | The function to be executed as a transaction. |
doRollback? | DoRollback | An optional callback that should return |
| returns | Return | Whatever value the provided transaction function returns. |
This method is useful for making bulk changes to the data in a Store, and when you don't want listeners to be called as you make each change. Changes are made silently during the transaction, and listeners relevant to the changes you have made will instead only be called when the whole transaction is complete.
If multiple changes are made to a piece of Store data throughout the transaction, a relevant listener will only be called with the final value (assuming it is different to the value at the start of the transaction), regardless of the changes that happened in between. For example, if a Cell had a value 'a' and then, within a transaction, it was changed to 'b' and then 'c', any CellListener registered for that cell would be called once as if there had been a single change from 'a' to 'c'.
Transactions can be nested. Relevant listeners will be called only when the outermost one completes.
The second, optional parameter, doRollback is a DoRollback callback that you can use to rollback the transaction if it did not complete to your satisfaction. See the DoRollback documentation for more details.
Examples
This example makes changes to two Cells, first outside, and secondly within, a transaction. In the second case, the Row listener is only called once.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.addRowListener('pets', 'fido', () => console.log('Fido changed'));
store
.setCell('pets', 'fido', 'color', 'brown')
.setCell('pets', 'fido', 'sold', false);
// -> 'Fido changed'
// -> 'Fido changed'
store.transaction(() =>
store
.setCell('pets', 'fido', 'color', 'walnut')
.setCell('pets', 'fido', 'sold', true),
);
// -> 'Fido changed'
This example makes multiple changes to one Cell. The Cell listener is called once - and with the final value - only if there is a net overall change.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.addCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId, newCell) => console.log(newCell),
);
store.transaction(() =>
store
.setCell('pets', 'fido', 'color', 'black')
.setCell('pets', 'fido', 'color', 'brown')
.setCell('pets', 'fido', 'color', 'walnut'),
);
// -> 'walnut'
store.transaction(() =>
store
.setCell('pets', 'fido', 'color', 'black')
.setCell('pets', 'fido', 'color', 'walnut'),
);
// -> undefined
// No net change during the transaction, so the listener is not called.
This example makes multiple changes to the Store, including some attempts to update a Cell and Value with invalid values. The doRollback callback receives information about the changes and invalid attempts, and then judges that the transaction should be rolled back to its original state.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({pets: {fido: {species: 'dog', color: 'brown'}}})
.setValues({open: true});
store.transaction(
() =>
store
.setCell('pets', 'fido', 'color', 'black')
.setCell('pets', 'fido', 'eyes', ['left', 'right'])
.setCell('pets', 'fido', 'info', {sold: null})
.setValue('open', false)
.setValue('employees', ['alice', 'bob']),
() => {
const [, , changedCells, invalidCells, changedValues, invalidValues] =
store.getTransactionLog();
console.log(store.getTables());
console.log(changedCells);
console.log(invalidCells);
console.log(changedValues);
console.log(invalidValues);
return invalidCells['pets'] != null;
},
);
// -> {pets: {fido: {species: 'dog', color: 'black'}}}
// -> {pets: {fido: {color: ['brown', 'black']}}}
// -> {pets: {fido: {eyes: [['left', 'right']], info: [{sold: null}]}}}
// -> {open: [true, false]}
// -> {employees: [['alice', 'bob']]}
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'brown'}}}
console.log(store.getValues());
// -> {open: true}
Since
v1.0.0
Listening for changes
addRowListener
The addRowListener method registers a listener function with the Store that will be called whenever data in a Row changes.
addRowListener(
tableId: IdOrNull,
rowId: IdOrNull,
listener: RowListener<Store>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
listener | RowListener<Store> | The function that will be called whenever data in the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a RowListener function, and will be called with a reference to the Store, the Id of the Table that changed, the Id of the Row that changed, and a GetCellChange function in case you need to inspect any changes that occurred.
You can either listen to a single Row (by specifying the Table Id and Row Id as the method's first two parameters) or changes to any Row (by providing null wildcards).
Both, either, or neither of the tableId and rowId parameters can be wildcarded with null. You can listen to a specific Row in a specific Table, any Row in a specific Table, a specific Row in any Table, or any Row in any Table.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any changes to a specific Row.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addRowListener(
'pets',
'fido',
(store, tableId, rowId, getCellChange) => {
console.log('fido row in pets table changed');
console.log(getCellChange('pets', 'fido', 'color'));
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'fido row in pets table changed'
// -> [true, 'brown', 'walnut']
store.delListener(listenerId);
This example registers a listener that responds to any changes to any Row.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addRowListener(
null,
null,
(store, tableId, rowId) => {
console.log(`${rowId} row in ${tableId} table changed`);
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'fido row in pets table changed'
store.setTable('species', {dog: {price: 5}});
// -> 'dog row in species table changed'
store.delListener(listenerId);
This example registers a listener that responds to any changes to a specific Row, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addRowListener(
'pets',
'fido',
(store, tableId, rowId) =>
store.setCell('meta', 'update', `${tableId}_${rowId}`, true),
true,
);
store.delCell('pets', 'fido', 'color');
console.log(store.getTable('meta'));
// -> {update: {pets_fido: true}}
store.delListener(listenerId);
Since
v1.0.0
addCellListener
The addCellListener method registers a listener function with the Store that will be called whenever data in a Cell changes.
addCellListener(
tableId: IdOrNull,
rowId: IdOrNull,
cellId: IdOrNull,
listener: CellListener<Store>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
cellId | IdOrNull | |
listener | CellListener<Store> | The function that will be called whenever data in the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a CellListener function, and will be called with a reference to the Store, the Id of the Table that changed, the Id of the Row that changed, the Id of the Cell that changed, the new Cell value, the old Cell value, and a GetCellChange function in case you need to inspect any changes that occurred.
You can either listen to a single Cell (by specifying the Table Id, Row Id, and Cell Id as the method's first three parameters) or changes to any Cell (by providing null wildcards).
All, some, or none of the tableId, rowId, and cellId parameters can be wildcarded with null. You can listen to a specific Cell in a specific Row in a specific Table, any Cell in any Row in any Table, for example - or every other combination of wildcards.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any changes to a specific Cell.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId, newCell, oldCell, getCellChange) => {
console.log('color cell in fido row in pets table changed');
console.log([oldCell, newCell]);
console.log(getCellChange('pets', 'fido', 'color'));
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'color cell in fido row in pets table changed'
// -> ['brown', 'walnut']
// -> [true, 'brown', 'walnut']
store.delListener(listenerId);
This example registers a listener that responds to any changes to any Cell.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addCellListener(
null,
null,
null,
(store, tableId, rowId, cellId) => {
console.log(
`${cellId} cell in ${rowId} row in ${tableId} table changed`,
);
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'color cell in fido row in pets table changed'
store.setTable('species', {dog: {price: 5}});
// -> 'price cell in dog row in species table changed'
store.delListener(listenerId);
This example registers a listener that responds to any changes to a specific Cell, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId) =>
store.setCell('meta', 'update', `${tableId}_${rowId}_${cellId}`, true),
true,
);
store.delCell('pets', 'fido', 'color');
console.log(store.getTable('meta'));
// -> {update: {pets_fido_color: true}}
store.delListener(listenerId);
Since
v1.0.0
addValueListener
The addValueListener method registers a listener function with the Store that will be called whenever data in a Value changes.
addValueListener(
valueId: IdOrNull,
listener: ValueListener<Store>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
valueId | IdOrNull | |
listener | ValueListener<Store> | The function that will be called whenever data in the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a ValueListener function, and will be called with a reference to the Store, the Id of the Value that changed, the new Value value, the old Value, and a GetValueChange function in case you need to inspect any changes that occurred.
You can either listen to a single Value (by specifying the Value Id) or changes to any Value (by providing a null wildcard).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any changes to a specific Value.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addValueListener(
'employees',
(store, valueId, newValue, oldValue, getValueChange) => {
console.log('employee value changed');
console.log([oldValue, newValue]);
console.log(getValueChange('employees'));
},
);
store.setValue('employees', 4);
// -> 'employee value changed'
// -> [3, 4]
// -> [true, 3, 4]
store.delListener(listenerId);
This example registers a listener that responds to any changes to any Value.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addValueListener(null, (store, valueId) => {
console.log(`${valueId} value changed`);
});
store.setValue('employees', 4);
// -> 'employees value changed'
store.setValue('open', false);
// -> 'open value changed'
store.delListener(listenerId);
This example registers a listener that responds to any changes to a specific Value, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addValueListener(
'employees',
(store) => store.setValue('updated', true),
true,
);
store.delValue('employees');
console.log(store.getValues());
// -> {open: true, updated: true}
store.delListener(listenerId);
Since
v3.0.0
Persisting stores
Persister
A Persister object lets you save and load Store data to and from different locations, or underlying storage types.
This is useful for preserving Store or MergeableStore data between browser sessions or reloads, saving or loading browser state to or from a server, or saving Store data to disk in a environment with filesystem access.
Creating a Persister depends on the choice of underlying storage where the data is to be stored. Options include the createSessionPersister function, the createLocalPersister function, the createRemotePersister function, and the createFilePersister function, as just simple examples. The createCustomPersister function can also be used to easily create a fully customized way to save and load Store data.
Using the values of the Persists enum, the generic parameter to the Persister indicates whether it can handle a regular Store, a MergeableStore, or either. Consult the table in the overall persisters module documentation to see current support for each. The different levels of support are also described for each of the types of Persister themselves.
A Persister lets you explicit save or load data, with the save method and the load method respectively. These methods are both asynchronous (since the underlying data storage may also be) and return promises. As a result you should use the await keyword to call them in a way that guarantees subsequent execution order.
When you don't want to deal with explicit persistence operations, a Persister object also provides automatic saving and loading. Automatic saving listens for changes to the Store and persists the data immediately. Automatic loading listens (or polls) for changes to the persisted data and reflects those changes in the Store.
You can start automatic saving or loading with the startAutoSave method and startAutoLoad method. Both are asynchronous since they will do an immediate save and load before starting to listen for subsequent changes. You can stop the behavior with the stopAutoSave method and stopAutoLoad method (which are synchronous).
You may often want to have both automatic saving and loading of a Store so that changes are constantly synchronized (allowing basic state preservation between browser tabs, for example). The framework has some basic provisions to prevent race conditions - for example it will not attempt to save data if it is currently loading it and vice-versa - and will sequentially schedule methods that could cause race conditions.
That said, be aware that you should always comprehensively test your persistence strategy to understand the opportunity for data loss (in the case of trying to save data to a server under poor network conditions, for example).
To help debug such issues, since v4.0.4, the create methods for all Persister objects take an optional onIgnoredError argument. This is a handler for the errors that the Persister would otherwise ignore when trying to save or load data (such as when handling corrupted stored data). It's recommended you use this for debugging persistence issues, but only in a development environment. Database-based Persister objects also take an optional onSqlCommand argument for logging commands and queries made to the underlying database.
Examples
This example creates a Store, persists it to the browser's session storage as a JSON string, changes the persisted data, updates the Store from it, and finally destroys the Persister again.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load();
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.destroy();
sessionStorage.clear();
This example creates a Store, and automatically saves and loads it to the browser's session storage as a JSON string. Changes to the Store data, or the persisted data (implicitly firing a StorageEvent), are reflected accordingly.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"felix":{"species":"cat"}}},{}]'
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
createDurableObjectSqlStoragePersister
The createDurableObjectSqlStoragePersister function creates a DurableObjectSqlStoragePersister object that can persist the Store to and from Cloudflare Durable Object SQLite storage.
createDurableObjectSqlStoragePersister(
store: MergeableStore,
sqlStorage: SqlStorage,
configOrStoreTableName?: string | DurableObjectSqlDatabasePersisterConfig,
onSqlCommand?: (sql: string, params?: any[]) => void,
onIgnoredError?: (error: any) => void,
): DurableObjectSqlStoragePersister| Type | Description | |
|---|---|---|
store | MergeableStore | The |
sqlStorage | SqlStorage | The Durable Object SQL storage to persist the |
configOrStoreTableName? | string | DurableObjectSqlDatabasePersisterConfig | A |
onSqlCommand? | (sql: string, params?: any[]) => void | An optional handler called every time the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | DurableObjectSqlStoragePersister | A reference to the new |
You will mostly use this within the createPersister method of a WsServerDurableObject.
This persister uses Cloudflare's SQLite storage backend, which provides better pricing and performance compared to the legacy Key-value storage backend.
Important Prerequisites: Before using this persister, you must configure your Durable Object class to use SQLite storage by adding a migration to your Wrangler configuration file. In your wrangler.toml, add the following.
[[migrations]]
tag = "v1"
new_sqlite_classes = ["YourDurableObjectClass"]
Or in your wrangler.json, add:
{
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["YourDurableObjectClass"]
}
]
}
For more details on Durable Object migrations, see the Cloudflare documentation.
A database Persister uses one of two modes: either a JSON serialization of the whole Store stored in a single row of a table (the default), a fragmented mode that stores each piece of data separately to avoid Cloudflare's 2MB row limit.
JSON Mode (Default): Stores the entire
Storeas JSON in a single database row. This is efficient for smaller stores but may hit Cloudflare's 2MB row limit for very large stores and uses fewer database writes.Fragmented Mode: Stores each table, row, cell, and value as separate database rows. Use this mode if you're concerned about hitting Cloudflare's 2MB row limit with large stores in JSON mode. This mode creates more database writes but avoids row size limitations.
The third argument is a DatabasePersisterConfig object that configures which of those modes to use, and settings for each. If the third argument is simply a string, it is used as the storeTableName property of the JSON serialization. If it is the string 'fragmented', it enables fragmented mode.
See the documentation for the DpcJson, DpcFragmented, and DpcTabular types for more information on how all of those modes can be configured.
As well as providing a reference to the Store or MergeableStore to persist, you must provide a sqlStorage parameter which identifies the Durable Object SQLite storage to persist it to.
Examples
This example creates a DurableObjectSqlStoragePersister object and persists the Store to Durable Object SQLite storage as a JSON serialization into the default tinybase table. It uses this within the createPersister method of a WsServerDurableObject instance.
import {createMergeableStore} from 'tinybase';
import {createDurableObjectSqlStoragePersister} from 'tinybase/persisters/persister-durable-object-sql-storage';
import {WsServerDurableObject} from 'tinybase/synchronizers/synchronizer-ws-server-durable-object';
export class MyDurableObject extends WsServerDurableObject {
createPersister() {
const store = createMergeableStore();
const persister = createDurableObjectSqlStoragePersister(
store,
this.ctx.storage.sql,
);
return persister;
}
}
This example creates a DurableObjectSqlStoragePersister object with a custom table name and SQL command logging for debugging.
import {createMergeableStore} from 'tinybase';
import {createDurableObjectSqlStoragePersister} from 'tinybase/persisters/persister-durable-object-sql-storage';
import {WsServerDurableObject} from 'tinybase/synchronizers/synchronizer-ws-server-durable-object';
export class MyDurableObject extends WsServerDurableObject {
createPersister() {
const store = createMergeableStore();
const persister = createDurableObjectSqlStoragePersister(
store,
this.ctx.storage.sql,
'my_app_store',
(sql, params) => console.log('SQL:', sql, params),
(error) => console.error('Persistence error:', error),
);
return persister;
}
}
This example creates a DurableObjectSqlStoragePersister object using fragmented mode to avoid Cloudflare's 2MB row limit for large stores.
import {createMergeableStore} from 'tinybase';
import {createDurableObjectSqlStoragePersister} from 'tinybase/persisters/persister-durable-object-sql-storage';
import {WsServerDurableObject} from 'tinybase/synchronizers/synchronizer-ws-server-durable-object';
export class MyDurableObject extends WsServerDurableObject {
createPersister() {
const store = createMergeableStore();
const persister = createDurableObjectSqlStoragePersister(
store,
this.ctx.storage.sql,
{mode: 'fragmented'},
);
return persister;
}
}
This example creates a DurableObjectSqlStoragePersister object using fragmented mode with a custom storage prefix.
import {createMergeableStore} from 'tinybase';
import {createDurableObjectSqlStoragePersister} from 'tinybase/persisters/persister-durable-object-sql-storage';
import {WsServerDurableObject} from 'tinybase/synchronizers/synchronizer-ws-server-durable-object';
export class MyDurableObject extends WsServerDurableObject {
createPersister() {
const store = createMergeableStore();
const persister = createDurableObjectSqlStoragePersister(
store,
this.ctx.storage.sql,
{mode: 'fragmented', storagePrefix: 'my_app_'},
);
return persister;
}
}
Since
v6.3.0
createLocalPersister
The createLocalPersister function creates a LocalPersister object that can persist the Store to the browser's local storage.
createLocalPersister(
store: Store | MergeableStore,
storageName: string,
onIgnoredError?: (error: any) => void,
): LocalPersister| Type | Description | |
|---|---|---|
store | Store | MergeableStore | The |
storageName | string | The unique key to identify the storage location. |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | LocalPersister | A reference to the new |
A LocalPersister supports both regular Store and MergeableStore objects.
As well as providing a reference to the Store to persist, you must provide a storageName parameter which is unique to your application. This is the key that the browser uses to identify the storage location.
Example
This example creates a LocalPersister object and persists the Store to the browser's local storage.
import {createStore} from 'tinybase';
import {createLocalPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createLocalPersister(store, 'pets');
await persister.save();
console.log(localStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
await persister.destroy();
localStorage.clear();
Since
v1.0.0
createPglitePersister
The createPglitePersister function creates a PglitePersister object that can persist the Store to a local PGlite database.
createPglitePersister(
store: Store | MergeableStore,
pglite: PGlite,
configOrStoreTableName?: string | DatabasePersisterConfig,
onSqlCommand?: (sql: string, params?: any[]) => void,
onIgnoredError?: (error: any) => void,
): Promise<PglitePersister>| Type | Description | |
|---|---|---|
store | Store | MergeableStore | The |
pglite | PGlite | The database connection that was returned from |
configOrStoreTableName? | string | DatabasePersisterConfig | A |
onSqlCommand? | (sql: string, params?: any[]) => void | An optional handler called every time the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | Promise<PglitePersister> | A reference to the new |
A PglitePersister supports regular Store objects, and can also be used to persist the metadata of a MergeableStore when using the JSON serialization mode, as described below.
As well as providing a reference to the Store to persist, you must provide a pglite parameter which identifies the database connection.
A database Persister uses one of two modes: either a JSON serialization of the whole Store stored in a single row of a table (the default), or a tabular mapping of Table Ids to database table names and vice-versa).
The third argument is a DatabasePersisterConfig object that configures which of those modes to use, and settings for each. If the third argument is simply a string, it is used as the storeTableName property of the JSON serialization.
See the documentation for the DpcJson and DpcTabular types for more information on how both of those modes can be configured.
This method is asynchronous because it will await the creation of dedicated new connections to the database. You will need to await a call to this function or handle the return type natively as a Promise.
Examples
This example creates a PglitePersister object and persists the Store to a local PGlite database as a JSON serialization into the my_tinybase table. It makes a change to the database directly and then reloads it back into the Store.
import {PGlite} from '@electric-sql/pglite';
import {createStore} from 'tinybase';
import {createPglitePersister} from 'tinybase/persisters/persister-pglite';
const pglite = await PGlite.create();
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = await createPglitePersister(
store,
pglite,
'my_tinybase',
);
await persister.save();
// Store will be saved to the database.
console.log((await pglite.query('SELECT * FROM my_tinybase;')).rows);
// -> [{_id: '_', store: '[{"pets":{"fido":{"species":"dog"}}},{}]'}]
await pglite.query(`UPDATE my_tinybase SET store = $1 WHERE _id = '_';`, [
'[{"pets":{"felix":{"species":"cat"}}},{}]',
]);
await persister.load();
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
await persister.destroy();
await pglite.close();
This example creates a PglitePersister object and persists the Store to a local PGlite database with tabular mapping.
import {PGlite} from '@electric-sql/pglite';
import {createStore} from 'tinybase';
import {createPglitePersister} from 'tinybase/persisters/persister-pglite';
const pglite = await PGlite.create();
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = await createPglitePersister(store, pglite, {
mode: 'tabular',
tables: {load: {pets: 'pets'}, save: {pets: 'pets'}},
});
await persister.save();
console.log((await pglite.query('SELECT * FROM pets;')).rows);
// -> [{_id: 'fido', species: '"dog"'}]
// Note that Cells and Values are JSON-encoded in PostgreSQL databases.
await pglite.query(
`INSERT INTO pets (_id, species) VALUES ('felix', '"cat"')`,
);
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
await persister.destroy();
await pglite.query('DROP TABLE IF EXISTS pets');
await pglite.close();
Since
5.2.0
createSessionPersister
The createSessionPersister function creates a SessionPersister object that can persist the Store to the browser's session storage.
createSessionPersister(
store: Store | MergeableStore,
storageName: string,
onIgnoredError?: (error: any) => void,
): SessionPersister| Type | Description | |
|---|---|---|
store | Store | MergeableStore | The |
storageName | string | The unique key to identify the storage location. |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | SessionPersister | A reference to the new |
A SessionPersister supports both regular Store and MergeableStore objects.
As well as providing a reference to the Store to persist, you must provide a storageName parameter which is unique to your application. This is the key that the browser uses to identify the storage location.
Example
This example creates a SessionPersister object and persists the Store to the browser's session storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
createSqliteWasmPersister
The createSqliteWasmPersister function creates a SqliteWasmPersister object that can persist the Store to a local SQLite database.
createSqliteWasmPersister(
store: Store | MergeableStore,
sqlite3: any,
db: any,
configOrStoreTableName?: string | DatabasePersisterConfig,
onSqlCommand?: (sql: string, params?: any[]) => void,
onIgnoredError?: (error: any) => void,
): SqliteWasmPersister| Type | Description | |
|---|---|---|
store | Store | MergeableStore | The |
sqlite3 | any | The WASM module that was returned from |
db | any | The database instance that was returned from |
configOrStoreTableName? | string | DatabasePersisterConfig | A |
onSqlCommand? | (sql: string, params?: any[]) => void | An optional handler called every time the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | SqliteWasmPersister | A reference to the new |
A SqliteWasmPersister supports regular Store objects, and can also be used to persist the metadata of a MergeableStore when using the JSON serialization mode, as described below.
As well as providing a reference to the Store to persist, you must provide sqlite3 and db parameters which identify the WASM module and database instance respectively.
A database Persister uses one of two modes: either a JSON serialization of the whole Store stored in a single row of a table (the default), or a tabular mapping of Table Ids to database table names and vice-versa).
The fourth argument is a DatabasePersisterConfig object that configures which of those modes to use, and settings for each. If the fourth argument is simply a string, it is used as the storeTableName property of the JSON serialization.
See the documentation for the DpcJson and DpcTabular types for more information on how both of those modes can be configured.
Examples
This example creates a SqliteWasmPersister object and persists the Store to a local SQLite database as a JSON serialization into the my_tinybase table. It makes a change to the database directly and then reloads it back into the Store.
import sqlite3InitModule from '@sqlite.org/sqlite-wasm';
import {createStore} from 'tinybase';
import {createSqliteWasmPersister} from 'tinybase/persisters/persister-sqlite-wasm';
const sqlite3 = await sqlite3InitModule();
const db = new sqlite3.oo1.DB(':memory:', 'c');
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSqliteWasmPersister(
store,
sqlite3,
db,
'my_tinybase',
);
await persister.save();
// Store will be saved to the database.
console.log(db.exec('SELECT * FROM my_tinybase;', {rowMode: 'object'}));
// -> [{_id: '_', store: '[{"pets":{"fido":{"species":"dog"}}},{}]'}]
db.exec(
'UPDATE my_tinybase SET store = ' +
`'[{"pets":{"felix":{"species":"cat"}}},{}]' WHERE _id = '_';`,
);
await persister.load();
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
await persister.destroy();
This example creates a SqliteWasmPersister object and persists the Store to a local SQLite database with tabular mapping.
import sqlite3InitModule from '@sqlite.org/sqlite-wasm';
import {createStore} from 'tinybase';
import {createSqliteWasmPersister} from 'tinybase/persisters/persister-sqlite-wasm';
const sqlite3 = await sqlite3InitModule();
const db = new sqlite3.oo1.DB(':memory:', 'c');
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSqliteWasmPersister(store, sqlite3, db, {
mode: 'tabular',
tables: {load: {pets: 'pets'}, save: {pets: 'pets'}},
});
await persister.save();
console.log(db.exec('SELECT * FROM pets;', {rowMode: 'object'}));
// -> [{_id: 'fido', species: 'dog'}]
db.exec(`INSERT INTO pets (_id, species) VALUES ('felix', 'cat')`);
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
await persister.destroy();
Since
v4.0.0
Synchronizing stores
Synchronizer
A Synchronizer object lets you synchronize MergeableStore data with another TinyBase client or system.
This is useful for sharing data between users, or between devices of a single user. This is especially valuable when there is the possibility that there has been a lack of immediate connectivity between clients and the synchronization requires some negotiation to orchestrate merging the MergeableStore objects together.
Creating a Synchronizer depends on the choice of underlying medium over which the synchronization will take place. Options include the createWsSynchronizer function (for a Synchronizer that will sync over web-sockets), and the createLocalSynchronizer function (for a Synchronizer that will sync two MergeableStore objects in memory on one system). The createCustomSynchronizer function can also be used to easily create a fully customized way to send and receive the messages of the synchronization protocol.
Note that, as an interface, it is an extension to the Persister interface, since they share underlying implementations. Think of a Synchronizer as 'persisting' your MergeableStore to another client (and vice-versa).
Example
This example creates two empty MergeableStore objects, creates a LocalSynchronizer for each, and starts synchronizing them. A change in one becomes evident in the other.
import {createMergeableStore} from 'tinybase';
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
const store1 = createMergeableStore();
const store2 = createMergeableStore();
const synchronizer1 = createLocalSynchronizer(store1);
const synchronizer2 = createLocalSynchronizer(store2);
await synchronizer1.startSync();
await synchronizer2.startSync();
store1.setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}}}
store2.setRow('pets', 'felix', {species: 'cat'});
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
await synchronizer1.destroy();
await synchronizer2.destroy();
Since
v5.0.0
WsServerDurableObject
The WsServerDurableObject is an overridden implementation of the DurableObject class, so you can have access to its members as well as the TinyBase-specific methods. If you are using the storage for other data, you may want to configure a prefix parameter to ensure you don't accidentally collide with TinyBase data.
Always remember to call the super implementations of the methods that TinyBase uses (the constructor, fetch, webSocketMessage, and webSocketClose) if you further override them.
Since
v5.4.0
createWsSynchronizer
The createWsSynchronizer function creates a WsSynchronizer object that can synchronize MergeableStore data to and from other MergeableStore instances via WebSockets facilitated by a WsServer.
createWsSynchronizer<WebSocketType>(
store: MergeableStore,
webSocket: WebSocketType,
requestTimeoutSeconds?: number,
onSend?: Send,
onReceive?: Receive,
onIgnoredError?: (error: any) => void,
): Promise<WsSynchronizer<WebSocketType>>| Type | Description | |
|---|---|---|
store | MergeableStore | The |
webSocket | WebSocketType | The WebSocket to send synchronization messages over. |
requestTimeoutSeconds? | number | An optional time in seconds that the |
onSend? | Send | An optional handler for the messages that this |
onReceive? | Receive | An optional handler for the messages that this |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | Promise<WsSynchronizer<WebSocketType>> | A reference to the new |
As well as providing a reference to the MergeableStore to persist, you must provide a configured WebSocket to send synchronization messages over.
Instead of the raw browser implementation of WebSocket, you may prefer to use the Reconnecting WebSocket wrapper so that if a client goes offline, it can easily re-establish a connection when it comes back online. Its API is compatible with this Synchronizer.
You can indicate how long the Synchronizer will wait for responses to message requests before timing out. A final set of optional handlers can be provided to help debug sends, receives, and errors respectively.
This method is asynchronous because it will await the websocket's connection to the server. You will need to await a call to this function or handle the return type natively as a Promise.
Example
This example creates two WsSynchronizer objects to synchronize one MergeableStore to another via a server.
import {createMergeableStore} from 'tinybase';
import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';
import {createWsServer} from 'tinybase/synchronizers/synchronizer-ws-server';
import {WebSocket, WebSocketServer} from 'ws';
const server = createWsServer(new WebSocketServer({port: 8047}));
const store1 = createMergeableStore();
const store2 = createMergeableStore();
const synchronizer1 = await createWsSynchronizer(
store1,
new WebSocket('ws://localhost:8047'),
);
const synchronizer2 = await createWsSynchronizer(
store2,
new WebSocket('ws://localhost:8047'),
);
await synchronizer1.startSync();
await synchronizer2.startSync();
store1.setTables({pets: {fido: {species: 'dog'}}});
store2.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
await synchronizer1.destroy();
await synchronizer2.destroy();
await server.destroy();
Since
v5.0.0
Using React
useCreateStore
The useCreateStore hook is used to create a Store within a React application with convenient memoization.
useCreateStore(
create: () => Store,
createDeps?: DependencyList,
): Store| Type | Description | |
|---|---|---|
create | () => Store | A function for performing the creation of the |
createDeps? | DependencyList | An optional array of dependencies for the |
| returns | Store | A reference to the |
It is possible to create a Store outside of the React app with the regular createStore function and pass it in, but you may prefer to create it within the app, perhaps inside the top-level component. To prevent a new Store being created every time the app renders or re-renders, the useCreateStore hook wraps the creation in a memoization.
The useCreateStore hook is a very thin wrapper around the React useMemo hook, defaulting to an empty array for its dependencies, so that by default, the creation only occurs once.
If your create function contains other dependencies, the changing of which should cause the Store to be recreated, you can provide them in an array in the optional second parameter, just as you would for any React hook with dependencies.
Examples
This example creates an empty Store at the top level of a React application. Even though the App component is rendered twice, the Store creation only occurs once by default.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useCreateStore} from 'tinybase/ui-react';
const App = () => {
const store = useCreateStore(() => {
console.log('Store created');
return createStore().setTables({pets: {fido: {species: 'dog'}}});
});
return <span>{store.getCell('pets', 'fido', 'species')}</span>;
};
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
// -> 'Store created'
root.render(<App />);
// No second Store creation
console.log(app.innerHTML);
// -> '<span>dog</span>'
This example creates an empty Store at the top level of a React application. The App component is rendered twice, each with a different top-level prop. The useCreateStore hook takes the fidoSpecies prop as a dependency, and so the Store is created again on the second render.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useCreateStore} from 'tinybase/ui-react';
const App = ({fidoSpecies}) => {
const store = useCreateStore(() => {
console.log(`Store created for fido as ${fidoSpecies}`);
return createStore().setTables({pets: {fido: {species: fidoSpecies}}});
}, [fidoSpecies]);
return <span>{store.getCell('pets', 'fido', 'species')}</span>;
};
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App fidoSpecies="dog" />);
// -> 'Store created for fido as dog'
console.log(app.innerHTML);
// -> '<span>dog</span>'
root.render(<App fidoSpecies="cat" />);
// -> 'Store created for fido as cat'
console.log(app.innerHTML);
// -> '<span>cat</span>'
Since
v1.0.0
useRow
The useRow hook returns an object containing the data of a single Row in a given Table, and registers a listener so that any changes to that result will cause a re-render.
useRow(
tableId: string,
rowId: string,
storeOrStoreId?: StoreOrStoreId,
): Row| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
storeOrStoreId? | StoreOrStoreId | The |
| returns | Row | An object containing the entire data of the |
A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useRow hook lets you indicate which Store to get data for: omit the final optional final parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Row will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store outside the application, which is used in the useRow hook by reference. A change to the data in the Store re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useRow} from 'tinybase/ui-react';
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => (
<span>{JSON.stringify(useRow('pets', 'fido', store))}</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>{"color":"brown"}</span>'
store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<span>{"color":"walnut"}</span>'
This example creates a Provider context into which a default Store is provided. A component within it then uses the useRow hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useRow} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useRow('pets', 'fido'))}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>{"color":"brown"}</span>'
This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useRow hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useRow} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useRow('pets', 'fido', 'petStore'))}</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>{"color":"brown"}</span>'
Since
v1.0.0
useCell
The useCell hook returns an object containing the value of a single Cell in a given Row, in a given Table, and registers a listener so that any changes to that result will cause a re-render.
useCell(
tableId: string,
rowId: string,
cellId: string,
storeOrStoreId?: StoreOrStoreId,
): CellOrUndefined| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
storeOrStoreId? | StoreOrStoreId | The |
| returns | CellOrUndefined | The value of the |
A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useCell hook lets you indicate which Store to get data for: omit the final optional final parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Cell will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store outside the application, which is used in the useCell hook by reference. A change to the data in the Store re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useCell} from 'tinybase/ui-react';
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => <span>{useCell('pets', 'fido', 'color', store)}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>brown</span>'
store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<span>walnut</span>'
This example creates a Provider context into which a default Store is provided. A component within it then uses the useCell hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useCell} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{useCell('pets', 'fido', 'color')}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>brown</span>'
This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useCell hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useCell} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useCell('pets', 'fido', 'color', 'petStore')}</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>brown</span>'
Since
v1.0.0
useValue
The useValue hook returns an object containing the data of a single Value in a Store, and registers a listener so that any changes to that result will cause a re-render.
useValue(
valueId: string,
storeOrStoreId?: StoreOrStoreId,
): ValueOrUndefined| Type | Description | |
|---|---|---|
valueId | string | |
storeOrStoreId? | StoreOrStoreId | The |
| returns | ValueOrUndefined | An object containing the entire data of the |
A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useValue hook lets you indicate which Store to get data for: omit the final optional final parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Value will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store outside the application, which is used in the useValue hook by reference. A change to the data in the Store re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useValue} from 'tinybase/ui-react';
const store = createStore().setValue('open', true);
const App = () => <span>{JSON.stringify(useValue('open', store))}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>true</span>'
store.setValue('open', false);
console.log(app.innerHTML);
// -> '<span>false</span>'
This example creates a Provider context into which a default Store is provided. A component within it then uses the useValue hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useValue} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useValue('open'))}</span>;
const store = createStore().setValue('open', true);
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>true</span>'
This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useValue hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useValue} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useValue('open', 'petStore'))}</span>
);
const store = createStore().setValue('open', true);
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>true</span>'
Since
v3.0.0
Inspector
The Inspector component renders a tool which allows you to view and edit the content of a Store in a debug web environment.
Inspector(props: InspectorProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | InspectorProps | The props for this component. |
| returns | ComponentReturnType | The rendering of the inspector tool. |
See the <Inspector /> demo for this component in action.
The component displays a nub in the corner of the screen which you may then click to interact with all the Store objects in the Provider component context.
The component's props identify the nub's initial location and panel state, though subsequent user changes to that will be preserved on each reload.
Example
This example creates a Provider context into which a default Store is provided. The Inspector component within it then renders the inspector tool.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider} from 'tinybase/ui-react';
import {Inspector} from 'tinybase/ui-react-inspector';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <Inspector />;
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
// ...
console.log(app.innerHTML.substring(0, 30));
// -> '<aside id="tinybaseInspector">'
Since
v5.0.0
Provider
The Provider component is used to wrap part of an application in a context that provides default objects to be used by hooks and components within.
Provider(props: ProviderProps & {children: ReactNode}): ComponentReturnType| Type | Description | |
|---|---|---|
props | ProviderProps & {children: ReactNode} | The props for this component. |
| returns | ComponentReturnType | A rendering of the child components. |
Store, Metrics, Indexes, Relationships, Queries, Checkpoints, Persister, and Synchronizer objects can be passed into the context of an application and used throughout. One of each type of object can be provided as a default within the context. Additionally, multiple of each type of object can be provided in an Id-keyed map to the ___ById props.
Provider contexts can be nested and the objects passed in will be merged. For example, if an outer context contains a default Metrics object and an inner context contains only a default Store, both the Metrics objects and the Store will be visible within the inner context. If the outer context contains a Store named by Id and the inner context contains a Store named by a different Id, both will be visible within the inner context.
Examples
This example creates a Provider context into which a Store and a Metrics object are provided, one by default, and one named by Id. Components within it then render content from both, without the need to have them passed as props.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createMetrics, createStore} from 'tinybase';
import {CellView, Provider, useMetric} from 'tinybase/ui-react';
const App = ({store, metrics}) => (
<Provider store={store} metricsById={{petStore: metrics}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
<CellView tableId="species" rowId="dog" cellId="price" />,
<CellView tableId="species" rowId="cat" cellId="price" />,
{useMetric('highestPrice', 'petStore')}
</span>
);
const store = createStore();
store.setTable('species', {dog: {price: 5}, cat: {price: 4}});
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} metrics={metrics} />);
console.log(app.innerHTML);
// -> '<span>5,4,5</span>'
This example creates nested Provider contexts into which Store and Metrics objects are provided, showing how visibility is merged.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createMetrics, createStore} from 'tinybase';
import {
CellView,
Provider,
useCreateStore,
useMetric,
} from 'tinybase/ui-react';
const App = ({petStore, metrics}) => (
<Provider storesById={{pet: petStore}} metrics={metrics}>
<OuterPane />
</Provider>
);
const OuterPane = () => {
const planetStore = useCreateStore(() =>
createStore().setTables({planets: {mars: {moons: 2}}}),
);
return (
<Provider storesById={{planet: planetStore}}>
<InnerPane />
</Provider>
);
};
const InnerPane = () => (
<span>
<CellView tableId="species" rowId="dog" cellId="price" store="pet" />,
{useMetric('highestPrice')},
<CellView
tableId="planets"
rowId="mars"
cellId="moons"
store="planet"
/>
</span>
);
const petStore = createStore();
petStore.setTable('species', {dog: {price: 5}, cat: {price: 4}});
const metrics = createMetrics(petStore);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App petStore={petStore} metrics={metrics} />);
console.log(app.innerHTML);
// -> '<span>5,5,2</span>'
Since
v1.0.0
store
The store module is the core of the TinyBase project and contains the types, interfaces, and functions to work with Store objects.
The main entry point to this module is the createStore function, which returns a new Store. From there, you can set and get data, register listeners, and use other modules to build an entire app around the state and tabular data within.
Since
v1.0.0
Interfaces
Store
A Store is the main location for keeping both tabular data and keyed values.
Create a Store easily with the createStore function. From there, you can set and get data, add listeners for when the data changes, set schemas, and so on.
A Store has two facets. It can contain keyed Values, and independently, it can contain tabular Tables data. These two facets have similar APIs but can be used entirely independently: you can use only tables, only keyed Values, or both tables and keyed Values - all in a single Store.
Keyed values
The keyed value support is best thought of as a flat JavaScript object. The Store contains a number of Value objects, each with a unique ID, and which is a string, boolean, or number.
{ // Store
"value1": "one", // Value (string)
"value2": true, // Value (boolean)
"value3": 3, // Value (number)
...
}
In its default form, a Store has no sense of a structured schema for the Values. However, you can optionally specify a ValuesSchema for a Store, which then usefully constrains and defaults the Values you can use.
Tabular data
The tabular data exists in a simple hierarchical structure:
- The
Storecontains a number ofTableobjects. - Each
Tablecontains a number ofRowobjects. - Each
Rowcontains a number ofCellobjects.
A Cell is a string, boolean, or number value.
The members of each level of this hierarchy are identified with a unique Id (which is a string). In other words you can naively think of a Store as a three-level-deep JavaScript object, keyed with strings:
{ // Store
"table1": { // Table
"row1": { // Row
"cell1": "one", // Cell (string)
"cell2": true, // Cell (boolean)
"cell3": 3, // Cell (number)
...
},
...
},
...
}
Again, by default Store has no sense of a structured schema. As long as they are unique within their own parent, the Id keys can each be any string you want. However, as you can optionally specify a TablesSchema for the tabular data in a Store, which then usefully constrains the Table and Cell Ids (and Cell values) you can use.
Setting and getting data
Every part of the Store can be accessed with getter methods. When you retrieve data from the Store, you are receiving a copy - rather than a reference - of it. This means that manipulating the data in the Store must be performed with the equivalent setter and deleter methods.
To benefit from the reactive behavior of the Store, you can also subscribe to changes on any part of it with 'listeners'. Registering a listener returns a listener Id (that you can use later to remove it with the delListener method), and it will then be called every time there is a change within the part of the hierarchy you're listening to.
This table shows the main ways you can set, get, and listen to, different types of data in a Store:
There are two extra methods to manipulate Row objects. The addRow method is like the setRow method but automatically assigns it a new unique Id. And the setPartialRow method lets you update multiple Cell values in a Row without affecting the others. There is a similar setPartialValues method to do the same for the Values in a Store.
You can listen to attempts to write invalid data to a Value or Cell with the addInvalidValueListener method or addInvalidCellListener method.
The transaction method is used to wrap multiple changes to the Store so that the relevant listeners only fire once.
The setJson method and the getJson method allow you to work with a JSON-encoded representation of the entire Store, which is useful for persisting it.
Finally, the callListener method provides a way for you to manually provoke a listener to be called, even if the underlying data hasn't changed. This is useful when you are using mutator listeners to guarantee that data conforms to programmatic conditions, and those conditions change such that you need to update the Store in bulk.
Read more about setting and changing data in The Basics guides, and about listeners in the Listening to Stores guide.
Creating a schema
You can set a ValuesSchema and a TablesSchema with the setValuesSchema method and setTablesSchema method respectively. A TablesSchema constrains the Table Ids the Store can have, and the types of Cell data in each Table. Each Cell requires its type to be specified, and can also take a default value for when it's not specified.
You can also get a serialization of the schemas out of the Store with the getSchemaJson method, and remove the schemas altogether with the delValuesSchema method and delTablesSchema method.
Read more about schemas in the Using Schemas guide.
Convenience methods
There are a few additional helper methods to make it easier to work with a Store. There are methods for easily checking the existence of a Table, Row, or Cell, and iterators that let you act on the children of a common parent:
| Checking existence | Iterator | |
|---|---|---|
Value | hasValue | forEachValue |
Table | hasTable | forEachTable |
Row | hasRow | forEachRow |
Cell | hasCell | forEachCell |
Since v4.3.23, you can add listeners for the change of existence of part of a Store. For example, the addHasValueListener method lets you listen for a Value being added or removed.
Finally, the getListenerStats method describes the current state of the Store's listeners for debugging purposes.
Example
This example shows a very simple lifecycle of a Store: from creation, to adding and getting some data, and then registering and removing a listener.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getRow('pets', 'fido'));
// -> {species: 'dog'}
store.setCell('pets', 'fido', 'color', 'brown');
console.log(store.getCell('pets', 'fido', 'color'));
// -> 'brown'
const listenerId = store.addTableListener('pets', () => {
console.log('changed');
});
store.setCell('pets', 'fido', 'sold', false);
// -> 'changed'
store.delListener(listenerId);
See also
- The Basics guides
- Using Schemas guides
- Hello World demos
- Todo App demos
Since
v1.0.0
Getter methods
getTables
The getTables method returns a Tables object containing the entire tabular data of the Store.
getTables(): TablesNote that this returns a copy of, rather than a reference to the underlying data, so changes made to the returned object are not made to the Store itself.
Examples
This example retrieves the tabular data in a Store.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}, species: {dog: {price: 5}}}
This example retrieves the Tables of an empty Store, returning an empty object.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getTables());
// -> {}
Since
v1.0.0
getTablesJson
The getTablesJson method returns a string serialization of all of the Tables in the Store.
getTablesJson(): stringExamples
This example serializes the contents of a Store.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getTablesJson());
// -> '{"pets":{"fido":{"species":"dog"}}}'
This example serializes the contents of an empty Store.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getTablesJson());
// -> '{}'
Since
v3.0.0
getTablesSchemaJson
The getTablesSchemaJson method returns a string serialization of the TablesSchema of the Store.
getTablesSchemaJson(): string| returns | string | A string serialization of the |
|---|
If no TablesSchema has been set on the Store (or if it has been removed with the delTablesSchema method), then it will return the serialization of an empty object, {}.
Examples
This example serializes the TablesSchema of a Store.
import {createStore} from 'tinybase';
const store = createStore().setTablesSchema({
pets: {
species: {type: 'string'},
sold: {type: 'boolean'},
},
});
console.log(store.getTablesSchemaJson());
// -> '{"pets":{"species":{"type":"string"},"sold":{"type":"boolean"}}}'
This example serializes the TablesSchema of an empty Store.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getTablesSchemaJson());
// -> '{}'
Since
v3.0.0
hasTables
The hasTables method returns a boolean indicating whether any Table objects exist in the Store.
hasTables(): boolean| returns | boolean | Whether any |
|---|
Example
This example shows simple existence checks.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.hasTables());
// -> false
store.setTables({pets: {fido: {species: 'dog'}}});
console.log(store.hasTables());
// -> true
Since
v1.0.0
hasTablesSchema
The hasTablesSchema method returns a boolean indicating whether the Store currently has a TablesSchema applied to it.
hasTablesSchema(): boolean| returns | boolean | Whether the |
|---|
Example
This example sets a TablesSchema and checks that it is present.
import {createStore} from 'tinybase';
const store = createStore().setTablesSchema({
pets: {
price: {type: 'number'},
},
});
console.log(store.hasTablesSchema());
// -> true
store.delTablesSchema();
console.log(store.hasTablesSchema());
// -> false
Since
v4.1.1
getTableIds
The getTableIds method returns the Ids of every Table in the Store.
getTableIds(): IdsNote that this returns a copy of, rather than a reference, to the list of Ids, so changes made to the list are not made to the Store itself.
Examples
This example retrieves the Table Ids in a Store.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
console.log(store.getTableIds());
// -> ['pets', 'species']
This example retrieves the Table Ids of an empty Store, returning an empty array.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getTableIds());
// -> []
Since
v1.0.0
getTable
The getTable method returns an object containing the entire data of a single Table in the Store.
getTable(tableId: string): TableNote that this returns a copy of, rather than a reference to the underlying data, so changes made to the returned object are not made to the Store itself.
Examples
This example retrieves the data in a single Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
console.log(store.getTable('pets'));
// -> {fido: {species: 'dog'}}
This example retrieves a Table that does not exist, returning an empty object.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getTable('employees'));
// -> {}
Since
v1.0.0
getTableCellIds
The getTableCellIds method returns the Ids of every Cell used across the whole Table.
getTableCellIds(tableId: string): Ids| Type | Description | |
|---|---|---|
tableId | string | |
| returns | Ids | An array of the |
Note that this returns a copy of, rather than a reference, to the list of Ids, so changes made to the list are not made to the Store itself.
Examples
This example retrieves the Cell Ids used across a whole Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', legs: 4},
cujo: {dangerous: true},
},
});
console.log(store.getTableCellIds('pets'));
// -> ['species', 'color', 'legs', 'dangerous']
This example retrieves the Cell Ids used across a Table that does not exist, returning an empty array.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getTableCellIds('species'));
// -> []
Since
v3.3.0
hasTable
The hasTable method returns a boolean indicating whether a given Table exists in the Store.
hasTable(tableId: string): boolean| Type | Description | |
|---|---|---|
tableId | string | |
| returns | boolean |
Example
This example shows two simple Table existence checks.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.hasTable('pets'));
// -> true
console.log(store.hasTable('employees'));
// -> false
Since
v1.0.0
hasTableCell
The hasTableCell method returns a boolean indicating whether a given Cell exists anywhere in a Table, not just in a specific Row.
hasTableCell(
tableId: string,
cellId: string,
): boolean| Type | Description | |
|---|---|---|
tableId | string | |
cellId | string | |
| returns | boolean |
Example
This example shows two simple Cell existence checks.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}, felix: {legs: 4}},
});
console.log(store.hasTableCell('pets', 'species'));
// -> true
console.log(store.hasTableCell('pets', 'legs'));
// -> true
console.log(store.hasTableCell('pets', 'color'));
// -> false
Since
v3.3.0
getRowIds
The getRowIds method returns the Ids of every Row in a given Table.
getRowIds(tableId: string): Ids| Type | Description | |
|---|---|---|
tableId | string | |
| returns | Ids |
Note that this returns a copy of, rather than a reference, to the list of Ids, so changes made to the list are not made to the Store itself.
Examples
This example retrieves the Row Ids in a Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
console.log(store.getRowIds('pets'));
// -> ['fido', 'felix']
This example retrieves the Row Ids of a Table that does not exist, returning an empty array.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getRowIds('employees'));
// -> []
Since
v1.0.0
getSortedRowIds
The getSortedRowIds method returns the Ids of every Row in a given Table, sorted according to the values in a specified Cell.
getSortedRowIds(
tableId: string,
cellId?: string,
descending?: boolean,
offset?: number,
limit?: number,
): Ids| Type | Description | |
|---|---|---|
tableId | string | |
cellId? | string | The |
descending? | boolean | Whether the sorting should be in descending order. |
offset? | number | The number of |
limit? | number | The maximum number of |
| returns | Ids | An array of the sorted |
The sorting of the rows is alphanumeric, and you can indicate whether it should be in descending order. The offset and limit parameters are used to paginate results, but default to 0 and undefined to return all available Row Ids if not specified.
Note that every call to this method will perform the sorting afresh - there is no caching of the results - and so you are advised to memoize the results yourself, especially when the Table is large. For a performant approach to tracking the sorted Row Ids when they change, use the addSortedRowIdsListener method.
If the Table does not exist, an empty array is returned.
Examples
This example retrieves sorted Row Ids in a Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
console.log(store.getSortedRowIds('pets', 'species'));
// -> ['felix', 'fido']
This example retrieves sorted Row Ids in a Table in reverse order.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'wolf'},
},
});
console.log(store.getSortedRowIds('pets', 'species', true));
// -> ['cujo', 'fido', 'felix']
This example retrieves two pages of Row Ids in a Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {price: 6},
felix: {price: 5},
mickey: {price: 2},
tom: {price: 4},
carnaby: {price: 3},
lowly: {price: 1},
},
});
console.log(store.getSortedRowIds('pets', 'price', false, 0, 2));
// -> ['lowly', 'mickey']
console.log(store.getSortedRowIds('pets', 'price', false, 2, 2));
// -> ['carnaby', 'tom']
This example retrieves Row Ids sorted by their own value, since the cellId parameter is undefined.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'wolf'},
},
});
console.log(store.getSortedRowIds('pets'));
// -> ['cujo', 'felix', 'fido']
This example retrieves the sorted Row Ids of a Table that does not exist, returning an empty array.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getSortedRowIds('employees'));
// -> []
Since
v2.0.0
When called with one object argument, the getSortedRowIds method destructures it to make it easier to skip optional parameters.
getSortedRowIds(args: SortedRowIdsArgs): Ids| Type | Description | |
|---|---|---|
args | SortedRowIdsArgs | A |
| returns | Ids | An array of the sorted |
Example
This example retrieves the first sorted Row Id in a Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'wolf'},
},
});
console.log(store.getSortedRowIds({tableId: 'pets', limit: 1}));
// -> ['cujo']
Since
v6.1.0
getRow
The getRow method returns an object containing the entire data of a single Row in a given Table.
getRow(
tableId: string,
rowId: string,
): RowNote that this returns a copy of, rather than a reference to the underlying data, so changes made to the returned object are not made to the Store itself.
Examples
This example retrieves the data in a single Row.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
console.log(store.getRow('pets', 'fido'));
// -> {species: 'dog'}
This example retrieves a Row that does not exist, returning an empty object.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getRow('pets', 'felix'));
// -> {}
Since
v1.0.0
getRowCount
The getRowCount method returns the count of the Row objects in a given Table.
getRowCount(tableId: string): number| Type | Description | |
|---|---|---|
tableId | string | |
| returns | number |
While this provides the same result as the length of Ids array returned from the getRowIds method, it is somewhat faster, and useful for efficient pagination.
Examples
This example retrieves the number of Row objects in the Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
console.log(store.getRowCount('pets'));
// -> 2
This example retrieves the Row Ids of a Table that does not exist, returning zero.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getRowCount('employees'));
// -> 0
Since
v4.1.0
hasRow
The hasRow method returns a boolean indicating whether a given Row exists in the Store.
hasRow(
tableId: string,
rowId: string,
): boolean| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
| returns | boolean |
Example
This example shows two simple Row existence checks.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.hasRow('pets', 'fido'));
// -> true
console.log(store.hasRow('pets', 'felix'));
// -> false
Since
v1.0.0
getCellIds
The getCellIds method returns the Ids of every Cell in a given Row in a given Table.
getCellIds(
tableId: string,
rowId: string,
): Ids| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
| returns | Ids |
Note that this returns a copy of, rather than a reference, to the list of Ids, so changes made to the list are not made to the Store itself.
Examples
This example retrieves the Cell Ids in a Row.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog', color: 'brown'},
},
});
console.log(store.getCellIds('pets', 'fido'));
// -> ['species', 'color']
This example retrieves the Cell Ids of a Row that does not exist, returning an empty array.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getCellIds('pets', 'felix'));
// -> []
Since
v1.0.0
getCell
The getCell method returns the value of a single Cell in a given Row, in a given Table.
getCell(
tableId: string,
rowId: string,
cellId: string,
): CellOrUndefined| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
| returns | CellOrUndefined | The value of the |
Examples
This example retrieves a single Cell.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
console.log(store.getCell('pets', 'fido', 'species'));
// -> 'dog'
This example retrieves a Cell that does not exist, returning undefined.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getCell('pets', 'fido', 'color'));
// -> undefined
Since
v1.0.0
hasCell
The hasCell method returns a boolean indicating whether a given Cell exists in a given Row in a given Table.
hasCell(
tableId: string,
rowId: string,
cellId: string,
): boolean| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
| returns | boolean | Whether a |
Example
This example shows two simple Cell existence checks.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.hasCell('pets', 'fido', 'species'));
// -> true
console.log(store.hasCell('pets', 'fido', 'color'));
// -> false
Since
v1.0.0
getValues
The getValues method returns an object containing the entire set of keyed Values in the Store.
getValues(): ValuesNote that this returns a copy of, rather than a reference to the underlying data, so changes made to the returned object are not made to the Store itself.
Examples
This example retrieves the set of keyed Values in the Store.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
console.log(store.getValues());
// -> {open: true, employees: 3}
This example retrieves Values from a Store that has none, returning an empty object.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getValues());
// -> {}
Since
v3.0.0
getValuesJson
The getValuesJson method returns a string serialization of all of the keyed Values in the Store.
getValuesJson(): stringExamples
This example serializes the keyed value contents of a Store.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
console.log(store.getValuesJson());
// -> '{"open":true}'
This example serializes the contents of an empty Store.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getValuesJson());
// -> '{}'
Since
v3.0.0
getValuesSchemaJson
The getValuesSchemaJson method returns a string serialization of the ValuesSchema of the Store.
getValuesSchemaJson(): string| returns | string | A string serialization of the |
|---|
If no ValuesSchema has been set on the Store (or if it has been removed with the delValuesSchema method), then it will return the serialization of an empty object, {}.
Examples
This example serializes the ValuesSchema of a Store.
import {createStore} from 'tinybase';
const store = createStore().setValuesSchema({
open: {type: 'boolean', default: false},
});
console.log(store.getValuesSchemaJson());
// -> '{"open":{"type":"boolean","default":false}}'
This example serializes the ValuesSchema of an empty Store.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getValuesSchemaJson());
// -> '{}'
Since
v3.0.0
hasValues
The hasValues method returns a boolean indicating whether any Values exist in the Store.
hasValues(): boolean| returns | boolean | Whether any |
|---|
Example
This example shows simple existence checks.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.hasValues());
// -> false
store.setValues({open: true});
console.log(store.hasValues());
// -> true
Since
v3.0.0
hasValuesSchema
The hasValuesSchema method returns a boolean indicating whether the Store currently has a ValuesSchema applied to it.
hasValuesSchema(): boolean| returns | boolean | Whether the |
|---|
Example
This example sets a ValuesSchema and checks that it is present.
import {createStore} from 'tinybase';
const store = createStore().setValuesSchema({open: {type: 'boolean'}});
console.log(store.hasValuesSchema());
// -> true
store.delValuesSchema();
console.log(store.hasValuesSchema());
// -> false
Since
v4.1.1
getValue
The getValue method returns a single keyed Value in the Store.
getValue(valueId: string): ValueOrUndefined| Type | Description | |
|---|---|---|
valueId | string | |
| returns | ValueOrUndefined | The |
Examples
This example retrieves a single Value.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
console.log(store.getValue('employees'));
// -> 3
This example retrieves a Value that does not exist, returning undefined.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
console.log(store.getValue('website'));
// -> undefined
Since
v3.0.0
getValueIds
The getValueIds method returns the Ids of every Value in a Store.
getValueIds(): IdsNote that this returns a copy of, rather than a reference, to the list of Ids, so changes made to the list are not made to the Store itself.
Examples
This example retrieves the Value Ids in a Store.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
console.log(store.getValueIds());
// -> ['open', 'employees']
This example retrieves the Value Ids of a Store that has had none set, returning an empty array.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getValueIds());
// -> []
Since
v3.0.0
hasValue
The hasValue method returns a boolean indicating whether a given Value exists in the Store.
hasValue(valueId: string): boolean| Type | Description | |
|---|---|---|
valueId | string | |
| returns | boolean |
Example
This example shows two simple Value existence checks.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
console.log(store.hasValue('open'));
// -> true
console.log(store.hasValue('employees'));
// -> false
Since
v3.0.0
getContent
The getContent method returns a Tables object and a Values object in an array, representing the entire content of the Store.
getContent(): ContentNote that this returns a copy of, rather than a reference to the underlying data, so changes made to the returned objects are not made to the Store itself.
Examples
This example retrieves the content of a Store.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({pets: {fido: {species: 'dog'}}})
.setValues({open: true, employees: 3});
console.log(store.getContent());
// -> [{pets: {fido: {species: 'dog'}}}, {open: true, employees: 3}]
This example retrieves the Tables and Values of an empty Store, returning empty objects.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getContent());
// -> [{}, {}]
Since
v4.0.0
getJson
The getJson method returns a string serialization of all the Store content: both the Tables and the keyed Values.
getJson(): stringFrom v3.0 onwards, the serialization is of an array with two entries. The first is the Tables object, the second the Values. In previous versions (before the existence of the Values data structure), it was a sole object of Tables.
Examples
This example serializes the tabular and keyed value contents of a Store.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({pets: {fido: {species: 'dog'}}})
.setValues({open: true});
console.log(store.getJson());
// -> '[{"pets":{"fido":{"species":"dog"}}},{"open":true}]'
This example serializes the contents of an empty Store.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getJson());
// -> '[{},{}]'
Since
v1.0.0
getSchemaJson
The getSchemaJson method returns a string serialization of both the TablesSchema and ValuesSchema of the Store.
getSchemaJson(): string| returns | string | A string serialization of the |
|---|
From v3.0 onwards, the serialization is of an array with two entries. The first is the TablesSchema object, the second the ValuesSchema. In previous versions (before the existence of the ValuesSchema data structure), it was a sole object of TablesSchema.
Examples
This example serializes the TablesSchema and ValuesSchema of a Store.
import {createStore} from 'tinybase';
const store = createStore()
.setTablesSchema({
pets: {
price: {type: 'number'},
},
})
.setValuesSchema({
open: {type: 'boolean'},
});
console.log(store.getSchemaJson());
// -> '[{"pets":{"price":{"type":"number"}}},{"open":{"type":"boolean"}}]'
This example serializes the TablesSchema and ValuesSchema of an empty Store.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getSchemaJson());
// -> '[{},{}]'
Since
v1.0.0
isMergeable
The isMergeable method lets you know if the Store is mergeable.
isMergeable(): boolean| returns | boolean | Whether the |
|---|
This will always return false for a Store, and true for a MergeableStore.
Since
v5.0.0
Setter methods
setTables
The setTables method takes an object and sets the entire tabular data of the Store.
setTables(tables: Tables): thisThis method will cause listeners to be called for any Table, Row, Cell, or Id changes resulting from it.
Any part of the provided object that is invalid (either according to the Tables type, or because it does not match a TablesSchema associated with the Store), will be ignored silently.
Assuming that at least some of the provided Tables object is valid, any data that was already present in the Store will be completely overwritten. If the object is completely invalid, no change will be made to the Store.
The method returns a reference to the Store so that subsequent operations can be chained in a fluent style.
Examples
This example sets the tabular data of a Store.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}, species: {dog: {price: 5}}}
This example attempts to set the tabular data of an existing Store with partly invalid, and then completely invalid, Tables objects.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.setTables({pets: {felix: {species: 'cat', bug: []}}});
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
store.setTables({meaning: 42});
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
Since
v1.0.0
setTablesJson
The setTablesJson method takes a string serialization of all of the Tables in the Store and attempts to update them to that.
setTablesJson(tablesJson: string): this| Type | Description | |
|---|---|---|
tablesJson | string | |
| returns | this | A reference to the |
If the JSON cannot be parsed, this will fail silently. If it can be parsed, it will then be subject to the same validation rules as the setTables method (according to the Tables type, and matching any TablesSchema associated with the Store).
Examples
This example sets the tabular contents of a Store from a serialization.
import {createStore} from 'tinybase';
const store = createStore();
store.setTablesJson('{"pets": {"fido": {"species": "dog"}}}');
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
This example attempts to set the tabular contents of a Store from an invalid serialization.
import {createStore} from 'tinybase';
const store = createStore();
store.setTablesJson('{"pets": {"fido": {');
console.log(store.getTables());
// -> {}
Since
v3.0.0
setTablesSchema
The setTablesSchema method lets you specify the TablesSchema of the tabular part of the Store.
setTablesSchema(tablesSchema: TablesSchema): this| Type | Description | |
|---|---|---|
tablesSchema | TablesSchema | The |
| returns | this | A reference to the |
Note that this may result in a change to data in the Store, as defaults are applied or as invalid Table, Row, or Cell objects are removed. These changes will fire any listeners to that data, as expected.
When no longer needed, you can also completely remove an existing TablesSchema with the delTablesSchema method.
Example
This example sets the TablesSchema of a Store after it has been created.
import {createStore} from 'tinybase';
const store = createStore().setTablesSchema({
pets: {
species: {type: 'string'},
sold: {type: 'boolean', default: false},
},
});
store.addRow('pets', {species: 'dog', color: 'brown', sold: 'maybe'});
console.log(store.getTables());
// -> {pets: {0: {species: 'dog', sold: false}}}
Since
v3.0.0
setTable
The setTable method takes an object and sets the entire data of a single Table in the Store.
setTable(
tableId: string,
table: Table,
): this| Type | Description | |
|---|---|---|
tableId | string | |
table | Table | The data of a single |
| returns | this | A reference to the |
This method will cause listeners to be called for any Table, Row, Cell, or Id changes resulting from it.
Any part of the provided object that is invalid (either according to the Table type, or because it does not match a TablesSchema associated with the Store), will be ignored silently.
Assuming that at least some of the provided Table object is valid, any data that was already present in the Store for that Table will be completely overwritten. If the object is completely invalid, no change will be made to the Store.
The method returns a reference to the Store so that subsequent operations can be chained in a fluent style.
Examples
This example sets the data of a single Table.
import {createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
This example attempts to set the data of an existing Store with partly invalid, and then completely invalid, Table objects.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.setTable('pets', {felix: {species: 'cat', bug: []}});
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
store.setTable('pets', {meaning: 42});
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
Since
v1.0.0
setRow
The setRow method takes an object and sets the entire data of a single Row in the Store.
setRow(
tableId: string,
rowId: string,
row: Row,
): this| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
row | Row | The data of a single |
| returns | this | A reference to the |
This method will cause listeners to be called for any Table, Row, Cell, or Id changes resulting from it.
Any part of the provided object that is invalid (either according to the Row type, or because it does not match a TablesSchema associated with the Store), will be ignored silently.
Assuming that at least some of the provided Row object is valid, any data that was already present in the Store for that Row will be completely overwritten. If the object is completely invalid, no change will be made to the Store.
The method returns a reference to the Store so that subsequent operations can be chained in a fluent style.
Examples
This example sets the data of a single Row.
import {createStore} from 'tinybase';
const store = createStore().setRow('pets', 'fido', {species: 'dog'});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
This example attempts to set the data of an existing Store with partly invalid, and then completely invalid, Row objects.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.setRow('pets', 'fido', {color: 'brown', bug: []});
console.log(store.getTables());
// -> {pets: {fido: {color: 'brown'}}}
store.setRow('pets', 'fido', 42);
console.log(store.getTables());
// -> {pets: {fido: {color: 'brown'}}}
Since
v1.0.0
addRow
The addRow method takes an object and creates a new Row in the Store, returning the unique Id assigned to it.
addRow(
tableId: string,
row: Row,
reuseRowIds?: boolean,
): undefined | string| Type | Description | |
|---|---|---|
tableId | string | |
row | Row | The data of a single |
reuseRowIds? | boolean | Whether |
| returns | undefined | string | A reference to the |
This method will cause listeners to be called for any Table, Row, Cell, or Id changes resulting from it.
Any part of the provided object that is invalid (either according to the Row type, or because it does not match a TablesSchema associated with the Store), will be ignored silently.
Assuming that at least some of the provided Row object is valid, a new Row will be created. If the object is completely invalid, no change will be made to the Store and the method will return undefined.
You should not guarantee the form of the unique Id that is generated when a Row is added to the Table. However it is likely to be a string representation of an increasing integer.
The reuseRowIds parameter defaults to true, which means that if you delete a Row and then add another, the Id will be re-used - unless you delete the entire Table, in which case all Row Ids will reset. Otherwise, if you specify reuseRowIds to be false, then the Id will be a monotonically increasing string representation of an increasing integer, regardless of any you may have previously deleted.
Examples
This example adds a single Row.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.addRow('pets', {species: 'dog'}));
// -> '0'
console.log(store.getTables());
// -> {pets: {'0': {species: 'dog'}}}
This example attempts to add Rows to an existing Store with partly invalid, and then completely invalid, Row objects.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {'0': {species: 'dog'}}});
console.log(store.addRow('pets', {species: 'cat', bug: []}));
// -> '1'
console.log(store.getTables());
// -> {pets: {'0': {species: 'dog'}, '1': {species: 'cat'}}}
console.log(store.addRow('pets', 42));
// -> undefined
console.log(store.getTables());
// -> {pets: {'0': {species: 'dog'}, '1': {species: 'cat'}}}
Since
v1.0.0
setPartialRow
The setPartialRow method takes an object and sets partial data of a single Row in the Store, leaving other Cell values unaffected.
setPartialRow(
tableId: string,
rowId: string,
partialRow: Row,
): this| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
partialRow | Row | The partial data of a single |
| returns | this | A reference to the |
This method will cause listeners to be called for any Table, Row, Cell, or Id changes resulting from it.
Any part of the provided object that is invalid (either according to the Row type, or because, when combined with the current Row data, it does not match a TablesSchema associated with the Store), will be ignored silently.
Assuming that at least some of the provided Row object is valid, it will be merged with the data that was already present in the Store. If the object is completely invalid, no change will be made to the Store.
The method returns a reference to the Store so that subsequent operations can be chained in a fluent style.
Examples
This example sets some of the data of a single Row.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
store.setPartialRow('pets', 'fido', {color: 'walnut', visits: 1});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'walnut', visits: 1}}}
This example attempts to set some of the data of an existing Store with partly invalid, and then completely invalid, Row objects.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.setPartialRow('pets', 'fido', {color: 'brown', bug: []});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'brown'}}}
store.setPartialRow('pets', 'fido', 42);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'brown'}}}
Since
v1.0.0
setCell
The setCell method sets the value of a single Cell in the Store.
setCell(
tableId: string,
rowId: string,
cellId: string,
cell: Cell | MapCell,
): this| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
cell | Cell | MapCell | The value of the |
| returns | this | A reference to the |
This method will cause listeners to be called for any Table, Row, Cell, or Id changes resulting from it.
If the Cell value is invalid (either because of its type, or because it does not match a TablesSchema associated with the Store), will be ignored silently.
As well as string, number, or boolean Cell types, this method can also take a MapCell function that takes the current Cell value as a parameter and maps it. This is useful if you want to efficiently increment a value without fetching it first, for example.
The method returns a reference to the Store so that subsequent operations can be chained in a fluent style.
Examples
This example sets the value of a single Cell.
import {createStore} from 'tinybase';
const store = createStore().setCell('pets', 'fido', 'species', 'dog');
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
This example sets the data of a single Cell by mapping the existing value.
import {createStore} from 'tinybase';
const increment = (cell) => cell + 1;
const store = createStore().setTables({pets: {fido: {visits: 1}}});
store.setCell('pets', 'fido', 'visits', increment);
console.log(store.getCell('pets', 'fido', 'visits'));
// -> 2
This example attempts to set the data of an existing Store with an invalid Cell value.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.setCell('pets', 'fido', 'bug', []);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
Since
v1.0.0
setPartialValues
The setPartialValues method takes an object and sets its Values in the Store, but leaving existing Values unaffected.
setPartialValues(partialValues: Values): thisThis method will cause listeners to be called for any Values or Id changes resulting from it.
Any part of the provided object that is invalid (either according to the Values type, or because, when combined with the current Values data, it does not match a ValuesSchema associated with the Store), will be ignored silently.
Assuming that at least some of the provided Values object is valid, it will be merged with the data that was already present in the Store. If the object is completely invalid, no change will be made to the Store.
The method returns a reference to the Store so that subsequent operations can be chained in a fluent style.
Examples
This example sets some of the keyed value data in a Store.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
store.setPartialValues({employees: 3});
console.log(store.getValues());
// -> {open: true, employees: 3}
This example attempts to set some of the data of an existing Store with partly invalid, and then completely invalid, Values objects.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
store.setPartialValues({employees: 3, bug: []});
console.log(store.getValues());
// -> {open: true, employees: 3}
store.setPartialValues(42);
console.log(store.getValues());
// -> {open: true, employees: 3}
Since
v3.0.0
setValues
The setValues method takes an object and sets all the Values in the Store.
setValues(values: Values): thisThis method will cause listeners to be called for any Value or Id changes resulting from it.
Any part of the provided object that is invalid (either according to the Values type, or because it does not match a ValuesSchema associated with the Store), will be ignored silently.
Assuming that at least some of the provided Values object is valid, any data that was already present in the Store for that Values will be completely overwritten. If the object is completely invalid, no change will be made to the Store.
The method returns a reference to the Store so that subsequent operations can be chained in a fluent style.
Examples
This example sets the Values of a Store.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
console.log(store.getValues());
// -> {open: true, employees: 3}
This example attempts to set the data of an existing Store with partly invalid, and then completely invalid, Values objects.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
store.setValues({employees: 3, bug: []});
console.log(store.getValues());
// -> {employees: 3}
store.setValues(42);
console.log(store.getValues());
// -> {employees: 3}
Since
v3.0.0
setValuesJson
The setValuesJson method takes a string serialization of all of the Values in the Store and attempts to update them to those values.
setValuesJson(valuesJson: string): this| Type | Description | |
|---|---|---|
valuesJson | string | |
| returns | this | A reference to the |
If the JSON cannot be parsed, this will fail silently. If it can be parsed, it will then be subject to the same validation rules as the setValues method (according to the Values type, and matching any ValuesSchema associated with the Store).
Examples
This example sets the keyed value contents of a Store from a serialization.
import {createStore} from 'tinybase';
const store = createStore();
store.setValuesJson('{"open": true}');
console.log(store.getValues());
// -> {open: true}
This example attempts to set the keyed value contents of a Store from an invalid serialization.
import {createStore} from 'tinybase';
const store = createStore();
store.setValuesJson('{"open": false');
console.log(store.getValues());
// -> {}
Since
v3.0.0
setValuesSchema
The setValuesSchema method lets you specify the ValuesSchema of the keyed Values part of the Store.
setValuesSchema(valuesSchema: ValuesSchema): this| Type | Description | |
|---|---|---|
valuesSchema | ValuesSchema | The |
| returns | this | A reference to the |
Note that this may result in a change to data in the Store, as defaults are applied or as invalid Values are removed. These changes will fire any listeners to that data, as expected.
When no longer needed, you can also completely remove an existing ValuesSchema with the delValuesSchema method.
Example
This example sets the ValuesSchema of a Store after it has been created.
import {createStore} from 'tinybase';
const store = createStore().setValuesSchema({
open: {type: 'boolean', default: false},
});
store.setValue('open', 'maybe');
console.log(store.getValues());
// -> {open: false}
Since
v3.0.0
setValue
The setValue method sets a single keyed Value in the Store.
setValue(
valueId: string,
value: Value | MapValue,
): thisThis method will cause listeners to be called for any Value, or Id changes resulting from it.
If the Value is invalid (either because of its type, or because it does not match a ValuesSchema associated with the Store), will be ignored silently.
As well as string, number, or boolean Value types, this method can also take a MapValue function that takes the current Value as a parameter and maps it. This is useful if you want to efficiently increment a value without fetching it first, for example.
The method returns a reference to the Store so that subsequent operations can be chained in a fluent style.
Examples
This example sets a single Value.
import {createStore} from 'tinybase';
const store = createStore().setValue('open', true);
console.log(store.getValues());
// -> {open: true}
This example sets the data of a single Value by mapping the existing Value.
import {createStore} from 'tinybase';
const increment = (value) => value + 1;
const store = createStore().setValues({employees: 3});
store.setValue('employees', increment);
console.log(store.getValue('employees'));
// -> 4
This example attempts to set an invalid Value.
import {createStore} from 'tinybase';
const store = createStore().setValues({employees: 3});
store.setValue('bug', []);
console.log(store.getValues());
// -> {employees: 3}
Since
v3.0.0
applyChanges
The applyChanges method applies a set of Changes to the Store.
applyChanges(changes: Changes): thisThis method will take a Changes object (which is available at the end of a transaction) and apply it to a Store. The most likely need to do this is to take the changes made during the transaction of one Store, and apply it to the content of another Store - such as when persisting and synchronizing data.
Any part of the provided Changes object are invalid (either because of its type, or because it does not match the schemas associated with the Store) will be ignored silently.
The method returns a reference to the Store so that subsequent operations can be chained in a fluent style.
Prior to v5.0, this method was named setTransactionChanges.
Example
This example applies a Changes object that sets a Cell and removes a Value.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({pets: {fido: {species: 'dog', color: 'brown'}}})
.setValues({open: true});
store.applyChanges([{pets: {fido: {color: 'black'}}}, {open: null}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'black'}}}
console.log(store.getValues());
// -> {}
Since
v5.0.0
setContent
The setContent method takes an array of two objects and sets the entire data of the Store.
setContent(content: Content | () => Content): this| Type | Description | |
|---|---|---|
content | Content | () => Content | An array containing the tabular and keyed-value data of the |
| returns | this | A reference to the |
This method will cause listeners to be called for any Table, Row, Cell, Value, or Id changes resulting from it.
Any part of the provided objects that are invalid (either according to the Tables or Values type, or because it does not match a TablesSchema or ValuesSchema associated with the Store), will be ignored silently.
Assuming that at least some of the provided Tables object or Values object is valid, any data that was already present in that part of the Store will be completely overwritten. If either object is completely invalid, no change will be made to the corresponding part of the Store.
The method returns a reference to the Store so that subsequent operations can be chained in a fluent style.
Since v5.4.2, this method can also take a function that returns the content.
Examples
This example sets the data of a Store.
import {createStore} from 'tinybase';
const store = createStore().setContent([
{pets: {fido: {species: 'dog'}}},
{open: true, employees: 3},
]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
console.log(store.getValues());
// -> {open: true, employees: 3}
This example sets the data of a Store with the results of a function.
import {createStore} from 'tinybase';
const store = createStore().setContent(() => [
{pets: {fido: {species: 'dog'}}},
{open: true, employees: 3},
]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
console.log(store.getValues());
// -> {open: true, employees: 3}
This example attempts to set the data of an existing Store with partly invalid, and then completely invalid objects.
import {createStore} from 'tinybase';
const store = createStore().setContent([
{pets: {fido: {species: 'dog'}}},
{open: true, employees: 3},
]);
store.setContent([{pets: {felix: {species: 'cat', bug: []}}}, '']);
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
console.log(store.getValues());
// -> {open: true, employees: 3}
store.setContent([{meaning: 42}]);
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
Since
v4.0.0
setJson
The setJson method takes a string serialization of all of the Tables and Values in the Store and attempts to update them to those values.
setJson(tablesAndValuesJson: string): this| Type | Description | |
|---|---|---|
tablesAndValuesJson | string | A string serialization of all of the |
| returns | this | A reference to the |
From v3.0 onwards, the serialization should be of an array with two entries. The first is the Tables object, the second the Values. In previous versions (before the existence of the Values data structure), it was a sole object of Tables. For backwards compatibility, if a serialization of a single object is provided, it will be treated as the Tables type.
If the JSON cannot be parsed, this will fail silently. If it can be parsed, it will then be subject to the same validation rules as the setTables method (according to the Tables type, and matching any TablesSchema associated with the Store), and the setValues method (according to the Values type, and matching any ValuesSchema associated with the Store).
Examples
This example sets the tabular and keyed value contents of a Store from a serialization.
import {createStore} from 'tinybase';
const store = createStore();
store.setJson('[{"pets": {"fido": {"species": "dog"}}}, {"open": true}]');
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
console.log(store.getValues());
// -> {open: true}
This example sets the tabular contents of a Store from a legacy single-object serialization (compatible with v2.x and earlier).
import {createStore} from 'tinybase';
const store = createStore();
store.setJson('{"pets": {"fido": {"species": "dog"}}}');
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
console.log(store.getValues());
// -> {}
This example attempts to set both the tabular and keyed value contents of a Store from an invalid serialization.
import {createStore} from 'tinybase';
const store = createStore();
store.setValuesJson('[{"pets": {"fido": {"species": "do');
console.log(store.getTables());
// -> {}
console.log(store.getValues());
// -> {}
Since
v1.0.0
setSchema
The setSchema method lets you specify the TablesSchema and ValuesSchema of the Store.
setSchema(
tablesSchema: TablesSchema,
valuesSchema?: ValuesSchema,
): this| Type | Description | |
|---|---|---|
tablesSchema | TablesSchema | The |
valuesSchema? | ValuesSchema | The |
| returns | this | A reference to the |
Note that this may result in a change to data in the Store, as defaults are applied or as invalid Table, Row, Cell, or Value objects are removed. These changes will fire any listeners to that data, as expected.
From v3.0 onwards, this method takes two arguments. The first is the TablesSchema object, the second the ValuesSchema. In previous versions (before the existence of the ValuesSchema data structure), only the first was present. For backwards compatibility the new second parameter is optional.
Examples
This example sets the TablesSchema and ValuesSchema of a Store after it has been created.
import {createStore} from 'tinybase';
const store = createStore().setSchema(
{
pets: {
species: {type: 'string'},
sold: {type: 'boolean', default: false},
},
},
{open: {type: 'boolean', default: false}},
);
store.addRow('pets', {species: 'dog', color: 'brown', sold: 'maybe'});
store.setValue('open', 'maybe');
console.log(store.getTables());
// -> {pets: {0: {species: 'dog', sold: false}}}
console.log(store.getValues());
// -> {open: false}
This example sets just the TablesSchema of a Store after it has been created.
import {createStore} from 'tinybase';
const store = createStore().setSchema({
pets: {
species: {type: 'string'},
sold: {type: 'boolean', default: false},
},
});
store.addRow('pets', {species: 'dog', color: 'brown', sold: 'maybe'});
console.log(store.getTables());
// -> {pets: {0: {species: 'dog', sold: false}}}
Since
v1.0.0
Listener methods
addHasTablesListener
The addHasTablesListener method registers a listener function with the Store that will be called when Tables as a whole are added to or removed from the Store.
addHasTablesListener(
listener: HasTablesListener<Store>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
listener | HasTablesListener<Store> | The function that will be called whenever |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a HasTablesListener function, and will be called with a reference to the Store. It is also given a flag to indicate whether Tables now exist (having not done previously), or do not (having done so previously).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to Tables being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasTablesListener((store, hasTables) => {
console.log('Tables ' + (hasTables ? 'added' : 'removed'));
});
store.delTables();
// -> 'Tables removed'
store.setTables({species: {dog: {price: 5}}});
// -> 'Tables added'
store.delListener(listenerId);
This example registers a listener that responds to Tables being added or removed, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore();
const listenerId = store.addHasTablesListener(
(store, hasTables) => store.setValue('hasTables', hasTables),
true,
);
store.setTables({species: {dog: {price: 5}}});
console.log(store.getValues());
// -> {hasTables: true}
store.delListener(listenerId);
Since
v4.4.0
addTablesListener
The addTablesListener method registers a listener function with the Store that will be called whenever data in the Store changes.
addTablesListener(
listener: TablesListener<Store>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
listener | TablesListener<Store> | The function that will be called whenever data in the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a TablesListener function, and will be called with a reference to the Store and a GetCellChange function in case you need to inspect any changes that occurred.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any changes to the whole Store.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTablesListener((store, getCellChange) => {
console.log('Tables changed');
console.log(getCellChange('pets', 'fido', 'color'));
});
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Tables changed'
// -> [true, 'brown', 'walnut']
store.delListener(listenerId);
This example registers a listener that responds to any changes to the whole Store, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTablesListener(
(store) => store.setCell('meta', 'update', 'store', true),
true,
);
store.delCell('pets', 'fido', 'color');
console.log(store.getTable('meta'));
// -> {update: {store: true}}
store.delListener(listenerId);
Since
v1.0.0
addTableIdsListener
The addTableIdsListener method registers a listener function with the Store that will be called whenever the Table Ids in the Store change.
addTableIdsListener(
listener: TableIdsListener<Store>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
listener | TableIdsListener<Store> | The function that will be called whenever the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a TableIdsListener function, and will be called with a reference to the Store.
By default, such a listener is only called when a Table is added or removed. To listen to all changes in the Store, use the addTablesListener method.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any change to the Table Ids.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addTableIdsListener((store) => {
console.log('Table Ids changed');
console.log(store.getTableIds());
});
store.setTable('species', {dog: {price: 5}});
// -> 'Table Ids changed'
// -> ['pets', 'species']
store.delListener(listenerId);
This example registers a listener that responds to any change to the Table Ids, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addTableIdsListener(
(store) => store.setCell('meta', 'update', 'store', true),
true, // mutator
);
store.setTable('species', {dog: {price: 5}});
console.log(store.getTable('meta'));
// -> {update: {store: true}}
store.delListener(listenerId);
Since
v1.0.0
addHasTableCellListener
The addHasTableCellListener method registers a listener function with the Store that will be called when a Cell is added to or removed from anywhere in a Table as a whole.
addHasTableCellListener(
tableId: IdOrNull,
cellId: IdOrNull,
listener: HasTableCellListener<Store>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
cellId | IdOrNull | |
listener | HasTableCellListener<Store> | The function that will be called whenever the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a HasTableCellListener function, and will be called with a reference to the Store, the Id of the Table that changed, and the Id of the Table Cell that changed. It is also given a flag to indicate whether the Cell now exists anywhere in the Table (having not done previously), or does not (having done so previously).
You can either listen to a single Table Cell being added or removed (by specifying the Table Id and Cell Id, as the method's first two parameters) or changes to any Table Cell (by providing null wildcards).
Both, either, or neither of the tableId and cellId parameters can be wildcarded with null. You can listen to a specific Row in a specific Table, any Row in a specific Table, a specific Row in any Table, or any Row in any Table.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to a specific Cell being added to or removed from the Table as a whole.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasTableCellListener(
'pets',
'color',
(store, tableId, cellId, hasTableCell) => {
console.log(
'color cell in pets table ' + (hasTableCell ? 'added' : 'removed'),
);
},
);
store.delCell('pets', 'fido', 'color');
// -> 'color cell in pets table removed'
store.setRow('pets', 'felix', {species: 'cat', color: 'brown'});
// -> 'color cell in pets table added'
store.delListener(listenerId);
This example registers a listener that responds to any Cell being added to or removed from the Table as a whole.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasTableCellListener(
null,
null,
(store, tableId, cellId, hasTableCell) => {
console.log(
`${cellId} cell in ${tableId} table ` +
(hasTableCell ? 'added' : 'removed'),
);
},
);
store.delCell('pets', 'fido', 'color');
// -> 'color cell in pets table removed'
store.setTable('species', {dog: {price: 5}});
// -> 'price cell in species table added'
store.delListener(listenerId);
This example registers a listener that responds to a specific Cell being added or removed, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasTableCellListener(
'pets',
'color',
(store, tableId, cellId) =>
store.setCell('meta', 'update', `${tableId}_${cellId}`, true),
true,
);
store.delRow('pets', 'fido');
console.log(store.getTable('meta'));
// -> {update: {pets_color: true}}
store.delListener(listenerId);
Since
v4.4.0
addHasTableListener
The addHasTableListener method registers a listener function with the Store that will be called when a Table is added to or removed from the Store.
addHasTableListener(
tableId: IdOrNull,
listener: HasTableListener<Store>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
listener | HasTableListener<Store> | The function that will be called whenever the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a HasTableListener function, and will be called with a reference to the Store and the Id of the Table that changed. It is also given a flag to indicate whether the Table now exists (having not done previously), or does not (having done so previously).
You can either listen to a single Table being added or removed (by specifying the Table Id as the method's first parameter) or changes to any Table (by providing a null wildcard).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to a specific Table being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasTableListener(
'pets',
(store, tableId, hasTable) => {
console.log('pets table ' + (hasTable ? 'added' : 'removed'));
},
);
store.delTable('pets');
// -> 'pets table removed'
store.setTable('pets', {fido: {species: 'dog', color: 'brown'}});
// -> 'pets table added'
store.delListener(listenerId);
This example registers a listener that responds to any Table being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasTableListener(
null,
(store, tableId, hasTable) => {
console.log(`${tableId} table ` + (hasTable ? 'added' : 'removed'));
},
);
store.delTable('pets');
// -> 'pets table removed'
store.setTable('species', {dog: {price: 5}});
// -> 'species table added'
store.delListener(listenerId);
This example registers a listener that responds to a specific Table being added or removed, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasTableListener(
'pets',
(store, tableId) => store.setCell('meta', 'update', tableId, true),
true,
);
store.delTable('pets', 'fido');
console.log(store.getTable('meta'));
// -> {update: {pets: true}}
store.delListener(listenerId);
Since
v4.4.0
addTableCellIdsListener
The addTableCellIdsListener method registers a listener function with the Store that will be called whenever the Cell Ids that appear anywhere in a Table change.
addTableCellIdsListener(
tableId: IdOrNull,
listener: TableCellIdsListener<Store>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
listener | TableCellIdsListener<Store> | The function that will be called whenever the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a TableCellIdsListener function, and will be called with a reference to the Store and the Id of the Table that changed.
By default, such a listener is only called when a Cell Id is added or removed from the whole of the Table. To listen to all changes in the Table, use the addTableListener method.
You can either listen to a single Table (by specifying its Id as the method's first parameter) or changes to any Table (by providing a null wildcard).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any change to the Cell Ids that appear anywhere in a Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTableCellIdsListener('pets', (store) => {
console.log('Cell Ids in pets table changed');
console.log(store.getTableCellIds('pets'));
});
store.setRow('pets', 'felix', {species: 'cat', legs: 4});
// -> 'Cell Ids in pets table changed'
// -> ['species', 'color', 'legs']
store.delListener(listenerId);
This example registers a listener that responds to any change to the Cell Ids that appear anywhere in any Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
species: {dog: {price: 5}},
});
const listenerId = store.addTableCellIdsListener(
null,
(store, tableId) => {
console.log(`Cell Ids in ${tableId} table changed`);
console.log(store.getTableCellIds(tableId));
},
);
store.setRow('pets', 'felix', {species: 'cat', legs: 4});
// -> 'Cell Ids in pets table changed'
// -> ['species', 'color', 'legs']
store.setRow('species', 'cat', {price: 4, friendly: true});
// -> 'Cell Ids in species table changed'
// -> ['price', 'friendly']
store.delListener(listenerId);
This example registers a listener that responds to the Cell Ids that appear anywhere in a Table, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTableCellIdsListener(
'pets',
(store, tableId) => store.setCell('meta', 'update', tableId, true),
true, // mutator
);
store.setRow('pets', 'felix', {species: 'cat', legs: 4});
console.log(store.getTable('meta'));
// -> {update: {pets: true}}
store.delListener(listenerId);
Since
v1.0.0
addTableListener
The addTableListener method registers a listener function with the Store that will be called whenever data in a Table changes.
addTableListener(
tableId: IdOrNull,
listener: TableListener<Store>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
listener | TableListener<Store> | The function that will be called whenever data in the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a TableListener function, and will be called with a reference to the Store, the Id of the Table that changed, and a GetCellChange function in case you need to inspect any changes that occurred.
You can either listen to a single Table (by specifying its Id as the method's first parameter) or changes to any Table (by providing a null wildcard).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any changes to a specific Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTableListener(
'pets',
(store, tableId, getCellChange) => {
console.log('pets table changed');
console.log(getCellChange('pets', 'fido', 'color'));
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'pets table changed'
// -> [true, 'brown', 'walnut']
store.delListener(listenerId);
This example registers a listener that responds to any changes to any Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTableListener(null, (store, tableId) => {
console.log(`${tableId} table changed`);
});
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'pets table changed'
store.setTable('species', {dog: {price: 5}});
// -> 'species table changed'
store.delListener(listenerId);
This example registers a listener that responds to any changes to a specific Table, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTableListener(
'pets',
(store, tableId) => store.setCell('meta', 'update', tableId, true),
true,
);
store.delCell('pets', 'fido', 'color');
console.log(store.getTable('meta'));
// -> {update: {pets: true}}
store.delListener(listenerId);
Since
v1.0.0
addRowIdsListener
The addRowIdsListener method registers a listener function with the Store that will be called whenever the Row Ids in a Table change.
addRowIdsListener(
tableId: IdOrNull,
listener: RowIdsListener<Store>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
listener | RowIdsListener<Store> | The function that will be called whenever the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a RowIdsListener function, and will be called with a reference to the Store and the Id of the Table that changed.
By default, such a listener is only called when a Row is added or removed. To listen to all changes in the Table, use the addTableListener method.
You can either listen to a single Table (by specifying its Id as the method's first parameter) or changes to any Table (by providing a null wildcard).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any change to the Row Ids of a specific Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addRowIdsListener('pets', (store) => {
console.log('Row Ids for pets table changed');
console.log(store.getRowIds('pets'));
});
store.setRow('pets', 'felix', {species: 'cat'});
// -> 'Row Ids for pets table changed'
// -> ['fido', 'felix']
store.delListener(listenerId);
This example registers a listener that responds to any change to the Row Ids of any Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addRowIdsListener(null, (store, tableId) => {
console.log(`Row Ids for ${tableId} table changed`);
console.log(store.getRowIds(tableId));
});
store.setRow('pets', 'felix', {species: 'cat'});
// -> 'Row Ids for pets table changed'
// -> ['fido', 'felix']
store.setRow('species', 'dog', {price: 5});
// -> 'Row Ids for species table changed'
// -> ['dog']
store.delListener(listenerId);
This example registers a listener that responds to any change to the Row Ids of a specific Table, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addRowIdsListener(
'pets',
(store, tableId) => store.setCell('meta', 'update', tableId, true),
true, // mutator
);
store.setRow('pets', 'felix', {species: 'cat'});
console.log(store.getTable('meta'));
// -> {update: {pets: true}}
store.delListener(listenerId);
Since
v1.0.0
addSortedRowIdsListener
The addSortedRowIdsListener method registers a listener function with the Store that will be called whenever sorted (and optionally, paginated) Row Ids in a Table change.
addSortedRowIdsListener(
tableId: string,
cellId: undefined | string,
descending: boolean,
offset: number,
limit: undefined | number,
listener: SortedRowIdsListener<Store>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
tableId | string | |
cellId | undefined | string | The |
descending | boolean | Whether the sorting should be in descending order. |
offset | number | The number of |
limit | undefined | number | The maximum number of |
listener | SortedRowIdsListener<Store> | The function that will be called whenever the sorted |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a SortedRowIdsListener function, and will be called with a reference to the Store, the Id of the Table whose Row Ids sorting changed, the Cell Id being used to sort them, whether descending or not, and the offset and limit of the number of Ids returned, for pagination purposes. It also receives the sorted array of Ids itself, so that you can use them in the listener without the additional cost of an explicit call to getSortedRowIds.
Such a listener is called when a Row is added or removed, but also when a value in the specified Cell (somewhere in the Table) has changed enough to change the sorting of the Row Ids.
Unlike most other listeners, you cannot provide wildcards (due to the cost of detecting changes to the sorting). You can only listen to a single specified Table, sorted by a single specified Cell.
The sorting of the rows is alphanumeric, and you can indicate whether it should be in descending order. The offset and limit parameters are used to paginate results, but default to 0 and undefined to return all available Row Ids if not specified.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any change to the sorted Row Ids of a specific Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
cujo: {species: 'wolf'},
felix: {species: 'cat'},
},
});
console.log(store.getSortedRowIds('pets', 'species', false));
// -> ['felix', 'cujo']
const listenerId = store.addSortedRowIdsListener(
'pets',
'species',
false,
0,
undefined,
(store, tableId, cellId, descending, offset, limit, sortedRowIds) => {
console.log(`Sorted Row Ids for ${tableId} table changed`);
console.log(sortedRowIds);
// ^ cheaper than calling getSortedRowIds again
},
);
store.setRow('pets', 'fido', {species: 'dog'});
// -> 'Sorted Row Ids for pets table changed'
// -> ['felix', 'fido', 'cujo']
store.delListener(listenerId);
This example registers a listener that responds to any change to a paginated section of the sorted Row Ids of a specific Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {price: 6},
felix: {price: 5},
mickey: {price: 2},
tom: {price: 4},
carnaby: {price: 3},
lowly: {price: 1},
},
});
const listenerId = store.addSortedRowIdsListener(
'pets',
'price',
false,
0,
3,
(store, tableId, cellId, descending, offset, limit, sortedRowIds) => {
console.log(`First three sorted Row Ids for ${tableId} table changed`);
console.log(sortedRowIds);
// ^ cheaper than calling getSortedRowIds again
},
);
console.log(store.getSortedRowIds('pets', 'price', false, 0, 3));
// -> ['lowly', 'mickey', 'carnaby']
store.setCell('pets', 'carnaby', 'price', 4.5);
// -> 'First three sorted Row Ids for pets table changed'
// -> ['lowly', 'mickey', 'tom']
store.delListener(listenerId);
This example registers a listener that responds to any change to the sorted Row Ids of a specific Table. The Row Ids are sorted by their own value, since the cellId parameter is explicitly undefined.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
console.log(store.getSortedRowIds('pets', undefined, false));
// -> ['felix', 'fido']
const listenerId = store.addSortedRowIdsListener(
'pets',
undefined,
false,
0,
undefined,
(store, tableId, cellId, descending, offset, limit, sortedRowIds) => {
console.log(`Sorted Row Ids for ${tableId} table changed`);
console.log(sortedRowIds);
// ^ cheaper than calling getSortedRowIds again
},
);
store.setRow('pets', 'cujo', {species: 'wolf'});
// -> 'Sorted Row Ids for pets table changed'
// -> ['cujo', 'felix', 'fido']
store.delListener(listenerId);
This example registers a listener that responds to a change in the sorting of the rows of a specific Table, even though the set of Ids themselves has not changed.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
console.log(store.getSortedRowIds('pets', 'species', false));
// -> ['felix', 'fido']
const listenerId = store.addSortedRowIdsListener(
'pets',
'species',
false,
0,
undefined,
(store, tableId, cellId, descending, offset, limit, sortedRowIds) => {
console.log(`Sorted Row Ids for ${tableId} table changed`);
console.log(sortedRowIds);
// ^ cheaper than calling getSortedRowIds again
},
);
store.setCell('pets', 'felix', 'species', 'tiger');
// -> 'Sorted Row Ids for pets table changed'
// -> ['fido', 'felix']
store.delListener(listenerId);
This example registers a listener that responds to any change to the sorted Row Ids of a specific Table, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
cujo: {species: 'wolf'},
felix: {species: 'cat'},
},
});
const listenerId = store.addSortedRowIdsListener(
'pets',
'species',
false,
0,
undefined,
(store, tableId) => store.setCell('meta', 'sorted', tableId, true),
true, // mutator
);
store.setRow('pets', 'fido', {species: 'dog'});
console.log(store.getTable('meta'));
// -> {sorted: {pets: true}}
store.delListener(listenerId);
Since
v2.0.0
When called with an object as the first argument, the addSortedRowIdsListener method destructures it to make it easier to skip optional parameters.
addSortedRowIdsListener(
args: SortedRowIdsArgs,
listener: SortedRowIdsListener<Store>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
args | SortedRowIdsArgs | A |
listener | SortedRowIdsListener<Store> | The function that will be called whenever the sorted |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
Example
This example registers a listener that responds to any change to the first of the sorted Row Ids of a specific Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {price: 6}, felix: {price: 5}},
});
const listenerId = store.addSortedRowIdsListener(
{tableId: 'pets', limit: 1},
(store, tableId, cellId, descending, offset, limit, sortedRowIds) => {
console.log(`First sorted Row Id for ${tableId} table changed`);
console.log(sortedRowIds);
// ^ cheaper than calling getSortedRowIds again
},
);
console.log(store.getSortedRowIds({tableId: 'pets', limit: 1}));
// -> ['felix']
store.setRow('pets', 'carnaby', {price: 4.5});
// -> 'First sorted Row Id for pets table changed'
// -> ['carnaby']
store.delListener(listenerId);
Since
v6.1.0
addHasRowListener
The addHasRowListener method registers a listener function with the Store that will be called when a Row is added to or removed from the Store.
addHasRowListener(
tableId: IdOrNull,
rowId: IdOrNull,
listener: HasRowListener<Store>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
listener | HasRowListener<Store> | The function that will be called whenever the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a HasRowListener function, and will be called with a reference to the Store, the Id of the Table that changed, and the Id of the Row that changed. It is also given a flag to indicate whether the Row now exists (having not done previously), or does not (having done so previously).
You can either listen to a single Row being added or removed (by specifying the Table Id and Row Id, as the method's first two parameters) or changes to any Row (by providing null wildcards).
Both, either, or neither of the tableId and rowId parameters can be wildcarded with null. You can listen to a specific Row in a specific Table, any Row in a specific Table, a specific Row in any Table, or any Row in any Table.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to a specific Row being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasRowListener(
'pets',
'fido',
(store, tableId, rowId, hasRow) => {
console.log(
'fido row in pets table ' + (hasRow ? 'added' : 'removed'),
);
},
);
store.delRow('pets', 'fido');
// -> 'fido row in pets table removed'
store.setRow('pets', 'fido', {species: 'dog', color: 'brown'});
// -> 'fido row in pets table added'
store.delListener(listenerId);
This example registers a listener that responds to any Row being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasRowListener(
null,
null,
(store, tableId, rowId, hasRow) => {
console.log(
`${rowId} row in ${tableId} table ` + (hasRow ? 'added' : 'removed'),
);
},
);
store.delRow('pets', 'fido');
// -> 'fido row in pets table removed'
store.setTable('species', {dog: {price: 5}});
// -> 'dog row in species table added'
store.delListener(listenerId);
This example registers a listener that responds to a specific Row being added or removed, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasRowListener(
'pets',
'fido',
(store, tableId, rowId) =>
store.setCell('meta', 'update', `${tableId}_${rowId}`, true),
true,
);
store.delRow('pets', 'fido');
console.log(store.getTable('meta'));
// -> {update: {pets_fido: true}}
store.delListener(listenerId);
Since
v4.4.0
addRowCountListener
The addRowCountListener method registers a listener function with the Store that will be called whenever the count of Row objects in a Table change.
addRowCountListener(
tableId: IdOrNull,
listener: RowCountListener<Store>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
listener | RowCountListener<Store> | The function that will be called whenever the number of |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a RowCountListener function, and will be called with a reference to the Store, the Id of the Table that changed, and the number of Row objects in the Table.
You can either listen to a single Table (by specifying its Id as the method's first parameter) or changes to any Table (by providing a null wildcard).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to a change in the number of Row objects in a specific Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addRowCountListener(
'pets',
(store, _tableId, count) => {
console.log('Row count for pets table changed to ' + count);
},
);
store.setRow('pets', 'felix', {species: 'cat'});
// -> 'Row count for pets table changed to 2'
store.delListener(listenerId);
This example registers a listener that responds to any change to a change in the number of Row objects of any Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addRowCountListener(
null,
(store, tableId, count) => {
console.log(`Row count for ${tableId} table changed to ${count}`);
},
);
store.setRow('pets', 'felix', {species: 'cat'});
// -> 'Row count for pets table changed to 2'
store.setRow('species', 'dog', {price: 5});
// -> 'Row count for species table changed to 1'
store.delListener(listenerId);
This example registers a listener that responds to any change to a change in the number of Row objects of a specific Table, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addRowCountListener(
'pets',
(store, tableId, count) =>
store.setCell('meta', 'update', tableId, count),
true, // mutator
);
store.setRow('pets', 'felix', {species: 'cat'});
console.log(store.getTable('meta'));
// -> {update: {pets: 2}}
store.delListener(listenerId);
Since
v4.1.0
addRowListener
The addRowListener method registers a listener function with the Store that will be called whenever data in a Row changes.
addRowListener(
tableId: IdOrNull,
rowId: IdOrNull,
listener: RowListener<Store>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
listener | RowListener<Store> | The function that will be called whenever data in the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a RowListener function, and will be called with a reference to the Store, the Id of the Table that changed, the Id of the Row that changed, and a GetCellChange function in case you need to inspect any changes that occurred.
You can either listen to a single Row (by specifying the Table Id and Row Id as the method's first two parameters) or changes to any Row (by providing null wildcards).
Both, either, or neither of the tableId and rowId parameters can be wildcarded with null. You can listen to a specific Row in a specific Table, any Row in a specific Table, a specific Row in any Table, or any Row in any Table.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any changes to a specific Row.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addRowListener(
'pets',
'fido',
(store, tableId, rowId, getCellChange) => {
console.log('fido row in pets table changed');
console.log(getCellChange('pets', 'fido', 'color'));
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'fido row in pets table changed'
// -> [true, 'brown', 'walnut']
store.delListener(listenerId);
This example registers a listener that responds to any changes to any Row.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addRowListener(
null,
null,
(store, tableId, rowId) => {
console.log(`${rowId} row in ${tableId} table changed`);
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'fido row in pets table changed'
store.setTable('species', {dog: {price: 5}});
// -> 'dog row in species table changed'
store.delListener(listenerId);
This example registers a listener that responds to any changes to a specific Row, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addRowListener(
'pets',
'fido',
(store, tableId, rowId) =>
store.setCell('meta', 'update', `${tableId}_${rowId}`, true),
true,
);
store.delCell('pets', 'fido', 'color');
console.log(store.getTable('meta'));
// -> {update: {pets_fido: true}}
store.delListener(listenerId);
Since
v1.0.0
addCellIdsListener
The addCellIdsListener method registers a listener function with the Store that will be called whenever the Cell Ids in a Row change.
addCellIdsListener(
tableId: IdOrNull,
rowId: IdOrNull,
listener: CellIdsListener<Store>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
listener | CellIdsListener<Store> | The function that will be called whenever the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a CellIdsListener function, and will be called with a reference to the Store, the Id of the Table, and the Id of the Row that changed.
By default, such a listener is only called when a Cell is added or removed. To listen to all changes in the Row, use the addRowListener method.
You can either listen to a single Row (by specifying the Table Id and Row Id as the method's first two parameters) or changes to any Row (by providing a null wildcard).
Both, either, or neither of the tableId and rowId parameters can be wildcarded with null. You can listen to a specific Row in a specific Table, any Row in a specific Table, a specific Row in any Table, or any Row in any Table.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any change to the Cell Ids of a specific Row.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addCellIdsListener('pets', 'fido', (store) => {
console.log('Cell Ids for fido row in pets table changed');
console.log(store.getCellIds('pets', 'fido'));
});
store.setCell('pets', 'fido', 'color', 'brown');
// -> 'Cell Ids for fido row in pets table changed'
// -> ['species', 'color']
store.delListener(listenerId);
This example registers a listener that responds to any change to the Cell Ids of any Row.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addCellIdsListener(
null,
null,
(store, tableId, rowId) => {
console.log(`Cell Ids for ${rowId} row in ${tableId} table changed`);
console.log(store.getCellIds(tableId, rowId));
},
);
store.setCell('pets', 'fido', 'color', 'brown');
// -> 'Cell Ids for fido row in pets table changed'
// -> ['species', 'color']
store.setCell('species', 'dog', 'price', 5);
// -> 'Cell Ids for dog row in species table changed'
// -> ['price']
store.delListener(listenerId);
This example registers a listener that responds to any change to the Cell Ids of a specific Row, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addCellIdsListener(
'pets',
'fido',
(store, tableId, rowId) =>
store.setCell('meta', 'update', `${tableId}_${rowId}`, true),
true, // mutator
);
store.setCell('pets', 'fido', 'color', 'brown');
console.log(store.getTable('meta'));
// -> {update: {pets_fido: true}}
store.delListener(listenerId);
Since
v1.0.0
addCellListener
The addCellListener method registers a listener function with the Store that will be called whenever data in a Cell changes.
addCellListener(
tableId: IdOrNull,
rowId: IdOrNull,
cellId: IdOrNull,
listener: CellListener<Store>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
cellId | IdOrNull | |
listener | CellListener<Store> | The function that will be called whenever data in the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a CellListener function, and will be called with a reference to the Store, the Id of the Table that changed, the Id of the Row that changed, the Id of the Cell that changed, the new Cell value, the old Cell value, and a GetCellChange function in case you need to inspect any changes that occurred.
You can either listen to a single Cell (by specifying the Table Id, Row Id, and Cell Id as the method's first three parameters) or changes to any Cell (by providing null wildcards).
All, some, or none of the tableId, rowId, and cellId parameters can be wildcarded with null. You can listen to a specific Cell in a specific Row in a specific Table, any Cell in any Row in any Table, for example - or every other combination of wildcards.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any changes to a specific Cell.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId, newCell, oldCell, getCellChange) => {
console.log('color cell in fido row in pets table changed');
console.log([oldCell, newCell]);
console.log(getCellChange('pets', 'fido', 'color'));
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'color cell in fido row in pets table changed'
// -> ['brown', 'walnut']
// -> [true, 'brown', 'walnut']
store.delListener(listenerId);
This example registers a listener that responds to any changes to any Cell.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addCellListener(
null,
null,
null,
(store, tableId, rowId, cellId) => {
console.log(
`${cellId} cell in ${rowId} row in ${tableId} table changed`,
);
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'color cell in fido row in pets table changed'
store.setTable('species', {dog: {price: 5}});
// -> 'price cell in dog row in species table changed'
store.delListener(listenerId);
This example registers a listener that responds to any changes to a specific Cell, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId) =>
store.setCell('meta', 'update', `${tableId}_${rowId}_${cellId}`, true),
true,
);
store.delCell('pets', 'fido', 'color');
console.log(store.getTable('meta'));
// -> {update: {pets_fido_color: true}}
store.delListener(listenerId);
Since
v1.0.0
addHasCellListener
The addHasCellListener method registers a listener function with the Store that will be called when a Cell is added to or removed from the Store.
addHasCellListener(
tableId: IdOrNull,
rowId: IdOrNull,
cellId: IdOrNull,
listener: HasCellListener<Store>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
cellId | IdOrNull | |
listener | HasCellListener<Store> | The function that will be called whenever the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a HasCellListener function, and will be called with a reference to the Store, the Id of the Table that changed, the Id of the Row that changed, and the Id of the Cell that changed. It is also given a flag to indicate whether the Cell now exists (having not done previously), or does not (having done so previously).
You can either listen to a single Cell being added or removed (by specifying the Table Id, Row Id, and Cell Id as the method's first three parameters) or changes to any Cell (by providing null wildcards).
All, some, or none of the tableId, rowId, and cellId parameters can be wildcarded with null. You can listen to a specific Cell in a specific Row in a specific Table, any Cell in any Row in any Table, for example - or every other combination of wildcards.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to a specific Cell being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId, hasCell) => {
console.log(
'color cell in fido row in pets table ' +
(hasCell ? 'added' : 'removed'),
);
},
);
store.delCell('pets', 'fido', 'color');
// -> 'color cell in fido row in pets table removed'
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'color cell in fido row in pets table added'
store.delListener(listenerId);
This example registers a listener that responds to any Cell being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasCellListener(
null,
null,
null,
(store, tableId, rowId, cellId, hasCell) => {
console.log(
`${cellId} cell in ${rowId} row in ${tableId} table ` +
(hasCell ? 'added' : 'removed'),
);
},
);
store.delCell('pets', 'fido', 'color');
// -> 'color cell in fido row in pets table removed'
store.setTable('species', {dog: {price: 5}});
// -> 'price cell in dog row in species table added'
store.delListener(listenerId);
This example registers a listener that responds to a specific Cell being added or removed, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId) =>
store.setCell('meta', 'update', `${tableId}_${rowId}_${cellId}`, true),
true,
);
store.delCell('pets', 'fido', 'color');
console.log(store.getTable('meta'));
// -> {update: {pets_fido_color: true}}
store.delListener(listenerId);
Since
v4.4.0
addInvalidCellListener
The addInvalidCellListener method registers a listener function with the Store that will be called whenever invalid data was attempted to be written to a Cell.
addInvalidCellListener(
tableId: IdOrNull,
rowId: IdOrNull,
cellId: IdOrNull,
listener: InvalidCellListener<Store>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
cellId | IdOrNull | |
listener | InvalidCellListener<Store> | The function that will be called whenever an attempt to write invalid data to the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is an InvalidCellListener function, and will be called with a reference to the Store, the Id of the Table, the Id of the Row, and the Id of Cell that was being attempted to be changed. It is also given the invalid value of the Cell, which could have been of absolutely any type. Since there could have been multiple failed attempts to set the Cell within a single transaction, this is an array containing each attempt, chronologically.
You can either listen to a single Cell (by specifying the Table Id, Row Id, and Cell Id as the method's first three parameters) or invalid attempts to change any Cell (by providing null wildcards).
All, some, or none of the tableId, rowId, and cellId parameters can be wildcarded with null. You can listen to a specific Cell in a specific Row in a specific Table, any Cell in any Row in any Table, for example - or every other combination of wildcards.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Special note should be made for how the listener will be called when a TablesSchema is present. The listener will be called:
- if a
Tableis being updated that is not specified in theTablesSchema, - if a
Cellis of the wrong type specified in theTablesSchema, - if a
Cellis omitted and is not defaulted in theTablesSchema, - or if an empty
Rowis provided and there are noCelldefaults in theTablesSchema.
The listener will not be called if a Cell that is defaulted in the TablesSchema is not provided, as long as all of the Cells that are not defaulted are provided.
To help understand all of these schema-based conditions, please see the TablesSchema example below.
Examples
This example registers a listener that responds to any invalid changes to a specific Cell.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addInvalidCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId, invalidCells) => {
console.log('Invalid color cell in fido row in pets table');
console.log(invalidCells);
},
);
store.setCell('pets', 'fido', 'color', {r: '96', g: '4B', b: '00'});
// -> 'Invalid color cell in fido row in pets table'
// -> [{r: '96', g: '4B', b: '00'}]
store.delListener(listenerId);
This example registers a listener that responds to any invalid changes to any Cell - in a Store without a TablesSchema. Note also how it then responds to cases where empty or invalid Row objects, or Table objects, or Tables objects are provided.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addInvalidCellListener(
null,
null,
null,
(store, tableId, rowId, cellId) => {
console.log(
`Invalid ${cellId} cell in ${rowId} row in ${tableId} table`,
);
},
);
store.setCell('pets', 'fido', 'color', {r: '96', g: '4B', b: '00'});
// -> 'Invalid color cell in fido row in pets table'
store.setTable('sales', {fido: {date: new Date()}});
// -> 'Invalid date cell in fido row in sales table'
store.setRow('pets', 'felix', {});
// -> 'Invalid undefined cell in felix row in pets table'
store.setRow('filter', 'name', /[a-z]?/);
// -> 'Invalid undefined cell in name row in filter table'
store.setRow('sales', '2021', {forecast: undefined});
// -> 'Invalid forecast cell in 2021 row in sales table'
store.addRow('filter', /[0-9]?/);
// -> 'Invalid undefined cell in undefined row in filter table'
store.setTable('raw', {});
// -> 'Invalid undefined cell in undefined row in raw table'
store.setTable('raw', ['row1', 'row2']);
// -> 'Invalid undefined cell in undefined row in raw table'
store.setTables(['table1', 'table2']);
// -> 'Invalid undefined cell in undefined row in undefined table'
store.delListener(listenerId);
This example registers a listener that responds to any invalid changes to any Cell - in a Store with a TablesSchema. Note how it responds to cases where missing parameters are provided for optional, and defaulted Cell values in a Row.
import {createStore} from 'tinybase';
const store = createStore().setTablesSchema({
pets: {
species: {type: 'string'},
color: {type: 'string', default: 'unknown'},
},
});
const listenerId = store.addInvalidCellListener(
null,
null,
null,
(store, tableId, rowId, cellId) => {
console.log(
`Invalid ${cellId} cell in ${rowId} row in ${tableId} table`,
);
},
);
store.setRow('sales', 'fido', {price: 5});
// -> 'Invalid price cell in fido row in sales table'
// The listener is called, because the sales Table is not in the schema
store.setRow('pets', 'felix', {species: true});
// -> 'Invalid species cell in felix row in pets table'
// The listener is called, because species is invalid...
console.log(store.getRow('pets', 'felix'));
// -> {color: 'unknown'}
// ...even though a Row was set with the default value
store.setRow('pets', 'fido', {color: 'brown'});
// -> 'Invalid species cell in fido row in pets table'
// The listener is called, because species is missing and not defaulted...
console.log(store.getRow('pets', 'fido'));
// -> {color: 'brown'}
// ...even though a Row was set
store.setRow('pets', 'rex', {species: 'dog'});
console.log(store.getRow('pets', 'rex'));
// -> {species: 'dog', color: 'unknown'}
// The listener is not called, because color is defaulted
store.delTables().setTablesSchema({
pets: {
species: {type: 'string'},
color: {type: 'string'},
},
});
store.setRow('pets', 'cujo', {});
// -> 'Invalid species cell in cujo row in pets table'
// -> 'Invalid color cell in cujo row in pets table'
// -> 'Invalid undefined cell in cujo row in pets table'
// The listener is called multiple times, because neither Cell is defaulted
// and the Row as a whole is empty
store.delListener(listenerId);
This example registers a listener that responds to any changes to a specific Cell, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addInvalidCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId, invalidCells) =>
store.setCell(
'meta',
'invalid_updates',
`${tableId}_${rowId}_${cellId}`,
JSON.stringify(invalidCells[0]),
),
true,
);
store.setCell('pets', 'fido', 'color', {r: '96', g: '4B', b: '00'});
console.log(store.getRow('meta', 'invalid_updates'));
// -> {'pets_fido_color': '{"r":"96","g":"4B","b":"00"}'}
store.delListener(listenerId);
Since
v1.1.0
addHasValuesListener
The addHasValuesListener method registers a listener function with the Store that will be called when Values as a whole are added to or removed from the Store.
addHasValuesListener(
listener: HasValuesListener<Store>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
listener | HasValuesListener<Store> | The function that will be called whenever |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a HasValuesListener function, and will be called with a reference to the Store. It is also given a flag to indicate whether Values now exist (having not done previously), or do not (having done so previously).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to Values being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addHasValuesListener((store, hasValues) => {
console.log('Values ' + (hasValues ? 'added' : 'removed'));
});
store.delValues();
// -> 'Values removed'
store.setValue('employees', 4);
// -> 'Values added'
store.delListener(listenerId);
This example registers a listener that responds to Values being added or removed, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore();
const listenerId = store.addHasValuesListener(
(store, hasValues) => store.setValue('hasValues', hasValues),
true,
);
store.setValue('employees', 4);
console.log(store.getValues());
// -> {employees: 4, hasValues: true}
store.delListener(listenerId);
Since
v4.4.0
addValuesListener
The addValuesListener method registers a listener function with the Store that will be called whenever the Values change.
addValuesListener(
listener: ValuesListener<Store>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
listener | ValuesListener<Store> | The function that will be called whenever data in the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a ValuesListener function, and will be called with a reference to the Store and a GetValueChange function in case you need to inspect any changes that occurred.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any changes to the Store's Values.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addValuesListener((store, getValueChange) => {
console.log('values changed');
console.log(getValueChange('employees'));
});
store.setValue('employees', 4);
// -> 'values changed'
// -> [true, 3, 4]
store.delListener(listenerId);
This example registers a listener that responds to any changes to the Store's Values, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addValuesListener(
(store) => store.setValue('updated', true),
true,
);
store.setValue('employees', 4);
console.log(store.getValues());
// -> {open: true, employees: 4, updated: true}
store.delListener(listenerId);
Since
v3.0.0
addHasValueListener
The addHasValueListener method registers a listener function with the Store that will be called when a Value is added to or removed from the Store.
addHasValueListener(
valueId: IdOrNull,
listener: HasValueListener<Store>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
valueId | IdOrNull | |
listener | HasValueListener<Store> | The function that will be called whenever the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a HasValueListener function, and will be called with a reference to the Store and the Id of Value that changed. It is also given a flag to indicate whether the Value now exists (having not done previously), or does not (having done so previously).
You can either listen to a single Value being added or removed (by specifying the Value Id) or any Value being added or removed (by providing a null wildcard).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to a specific Value being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addHasValueListener(
'employees',
(store, valueId, hasValue) => {
console.log('employee value ' + (hasValue ? 'added' : 'removed'));
},
);
store.delValue('employees');
// -> 'employee value removed'
store.setValue('employees', 4);
// -> 'employee value added'
store.delListener(listenerId);
This example registers a listener that responds to any Value being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addHasValueListener(
null,
(store, valueId, hasValue) => {
console.log(valueId + ' value ' + (hasValue ? 'added' : 'removed'));
},
);
store.delValue('employees');
// -> 'employees value removed'
store.setValue('website', 'https://pets.com');
// -> 'website value added'
store.delListener(listenerId);
This example registers a listener that responds to a specific Value being added or removed, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addHasValueListener(
'employees',
(store) => store.setValue('updated', true),
true,
);
store.delValue('employees');
console.log(store.getValues());
// -> {open: true, updated: true}
store.delListener(listenerId);
Since
v4.4.0
addInvalidValueListener
The addInvalidValueListener method registers a listener function with the Store that will be called whenever invalid data was attempted to be written to a Value.
addInvalidValueListener(
valueId: IdOrNull,
listener: InvalidValueListener<Store>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
valueId | IdOrNull | |
listener | InvalidValueListener<Store> | The function that will be called whenever an attempt to write invalid data to the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is an InvalidValueListener function, and will be called with a reference to the Store and the Id of Value that was being attempted to be changed. It is also given the invalid value of the Value, which could have been of absolutely any type. Since there could have been multiple failed attempts to set the Value within a single transaction, this is an array containing each attempt, chronologically.
You can either listen to a single Value (by specifying the Value Id as the method's first parameter) or invalid attempts to change any Value (by providing a null wildcard).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Special note should be made for how the listener will be called when a ValuesSchema is present. The listener will be called:
- if a
Valueis being updated that is not specified in theValuesSchema, - if a
Valueis of the wrong type specified in theValuesSchema, - or if a
Valueis omitted when using setValues that is not defaulted in theValuesSchema.
The listener will not be called if a Value that is defaulted in the ValuesSchema is not provided, as long as all of the Values that are not defaulted are provided.
To help understand all of these schema-based conditions, please see the ValuesSchema example below.
Examples
This example registers a listener that responds to any invalid changes to a specific Value.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
const listenerId = store.addInvalidValueListener(
'open',
(store, valueId, invalidValues) => {
console.log('Invalid open value');
console.log(invalidValues);
},
);
store.setValue('open', {yes: true});
// -> 'Invalid open value'
// -> [{yes: true}]
store.delListener(listenerId);
This example registers a listener that responds to any invalid changes to any Value - in a Store without a ValuesSchema. Note also how it then responds to cases where an empty Values object is provided.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
const listenerId = store.addInvalidValueListener(
null,
(store, valueId) => {
console.log(`Invalid ${valueId} value`);
},
);
store.setValue('open', {yes: true});
// -> 'Invalid open value'
store.setValue('employees', ['alice', 'bob']);
// -> 'Invalid employees value'
store.setValues('pets', 'felix', {});
// -> 'Invalid undefined value'
store.delListener(listenerId);
This example registers a listener that responds to any invalid changes to any Value - in a Store with a ValuesSchema. Note how it responds to cases where missing parameters are provided for optional, and defaulted Values.
import {createStore} from 'tinybase';
const store = createStore().setValuesSchema({
open: {type: 'boolean', default: false},
employees: {type: 'number'},
});
console.log(store.getValues());
// -> {open: false}
const listenerId = store.addInvalidValueListener(
null,
(store, valueId) => {
console.log(`Invalid ${valueId} value`);
},
);
store.setValue('website', true);
// -> 'Invalid website value'
// The listener is called, because the website Value is not in the schema
store.setValue('open', 'yes');
// -> 'Invalid open value'
// The listener is called, because 'open' is invalid...
console.log(store.getValues());
// -> {open: false}
// ...even though it is still present with the default value
store.setValues({open: true});
// -> 'Invalid employees value'
// The listener is called because employees is missing and not defaulted...
console.log(store.getValues());
// -> {open: true}
// ...even though the Values were set
store.setValues({employees: 3});
console.log(store.getValues());
// -> {open: false, employees: 3}
// The listener is not called, because 'open' is defaulted
store.setValuesSchema({
open: {type: 'boolean'},
employees: {type: 'number'},
});
store.setValues({});
// -> 'Invalid open value'
// -> 'Invalid employees value'
// -> 'Invalid undefined value'
// The listener is called multiple times, because neither Value is
// defaulted and the Values as a whole were empty
store.delListener(listenerId);
This example registers a listener that responds to any changes to a specific Value, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
const listenerId = store.addInvalidValueListener(
'open',
(store, valueId, invalidValues) =>
store.setValue('invalid_updates', JSON.stringify(invalidValues[0])),
true,
);
store.setValue('open', {yes: true});
console.log(store.getValue('invalid_updates'));
// -> '{"yes":true}'
store.delListener(listenerId);
Since
v3.0.0
addValueIdsListener
The addValueIdsListener method registers a listener function with the Store that will be called whenever the Value Ids in a Store change.
addValueIdsListener(
listener: ValueIdsListener<Store>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
listener | ValueIdsListener<Store> | The function that will be called whenever the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a ValueIdsListener function, and will be called with a reference to the Store.
By default, such a listener is only called when a Value is added or removed. To listen to all changes in the Values, use the addValuesListener method.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any change to the Value Ids.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
const listenerId = store.addValueIdsListener((store) => {
console.log('Value Ids changed');
console.log(store.getValueIds());
});
store.setValue('employees', 3);
// -> 'Value Ids changed'
// -> ['open', 'employees']
store.delListener(listenerId);
This example registers a listener that responds to any change to the Value Ids, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
const listenerId = store.addValueIdsListener(
(store) => store.setValue('updated', true),
true, // mutator
);
store.setValue('employees', 3);
console.log(store.getValues());
// -> {open: true, employees: 3, updated: true}
store.delListener(listenerId);
Since
v3.0.0
addValueListener
The addValueListener method registers a listener function with the Store that will be called whenever data in a Value changes.
addValueListener(
valueId: IdOrNull,
listener: ValueListener<Store>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
valueId | IdOrNull | |
listener | ValueListener<Store> | The function that will be called whenever data in the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a ValueListener function, and will be called with a reference to the Store, the Id of the Value that changed, the new Value value, the old Value, and a GetValueChange function in case you need to inspect any changes that occurred.
You can either listen to a single Value (by specifying the Value Id) or changes to any Value (by providing a null wildcard).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any changes to a specific Value.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addValueListener(
'employees',
(store, valueId, newValue, oldValue, getValueChange) => {
console.log('employee value changed');
console.log([oldValue, newValue]);
console.log(getValueChange('employees'));
},
);
store.setValue('employees', 4);
// -> 'employee value changed'
// -> [3, 4]
// -> [true, 3, 4]
store.delListener(listenerId);
This example registers a listener that responds to any changes to any Value.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addValueListener(null, (store, valueId) => {
console.log(`${valueId} value changed`);
});
store.setValue('employees', 4);
// -> 'employees value changed'
store.setValue('open', false);
// -> 'open value changed'
store.delListener(listenerId);
This example registers a listener that responds to any changes to a specific Value, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addValueListener(
'employees',
(store) => store.setValue('updated', true),
true,
);
store.delValue('employees');
console.log(store.getValues());
// -> {open: true, updated: true}
store.delListener(listenerId);
Since
v3.0.0
addDidFinishTransactionListener
The addDidFinishTransactionListener method registers a listener function with the Store that will be called just after other non-mutating listeners are called at the end of the transaction.
addDidFinishTransactionListener(listener: TransactionListener<Store>): string| Type | Description | |
|---|---|---|
listener | TransactionListener<Store> | The function that will be called after the end of a transaction. |
| returns | string | A unique |
This is useful if you need to know that a set of listeners have just been called at the end of a transaction, perhaps to batch their consequences together.
The provided TransactionListener will receive a reference to the Store and two booleans to indicate whether Cell or Value data has been touched during the transaction. The two flags is intended as a hint about whether non-mutating listeners might have been called at the end of the transaction.
Here, 'touched' means that Cell or Value data has either been changed, or changed and then changed back to its original value during the transaction. The exception is a transaction that has been rolled back, for which the value of cellsTouched and valuesTouched in the listener will be false because all changes have been reverted.
Note that a TransactionListener added to the Store with this method cannot mutate the Store itself, and attempts to do so will fail silently.
Example
This example registers a listener that is called at the end of the transaction, just after its listeners have been called. The transactions shown here variously change, touch, and rollback cells, demonstrating how the cellsTouched and valuesTouched parameters in the listener work.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
})
.setValues({open: true, employees: 3});
const listenerId = store.addDidFinishTransactionListener((store) => {
const [cellsTouched, valuesTouched] = store.getTransactionLog() ?? {};
console.log(`Cells/Values touched: ${cellsTouched}/${valuesTouched}`);
});
const listenerId2 = store.addTablesListener(() =>
console.log('Tables changed'),
);
const listenerId3 = store.addValuesListener(() =>
console.log('Values changed'),
);
store.transaction(() =>
store.setCell('pets', 'fido', 'color', 'brown').setValue('employees', 3),
);
// -> 'Cells/Values touched: false/false'
store.transaction(() => store.setCell('pets', 'fido', 'color', 'walnut'));
// -> 'Tables changed'
// -> 'Cells/Values touched: true/false'
store.transaction(() => store.setValue('employees', 4));
// -> 'Values changed'
// -> 'Cells/Values touched: false/true'
store.transaction(() => {
store
.setRow('pets', 'felix', {species: 'cat'})
.delRow('pets', 'felix')
.setValue('city', 'London')
.delValue('city');
});
// -> 'Cells/Values touched: true/true'
// But no Tables or Values listeners fired since there are no net changes.
store.transaction(
() =>
store
.setRow('pets', 'felix', {species: 'cat'})
.setValue('city', 'London'),
() => true,
);
// -> 'Cells/Values touched: false/false'
// Transaction was rolled back.
store.callListener(listenerId);
// -> 'Cells/Values touched: false/false'
// It is meaningless to call this listener directly.
store
.delListener(listenerId)
.delListener(listenerId2)
.delListener(listenerId3);
Since
v1.3.0
addStartTransactionListener
The addStartTransactionListener method registers a listener function with the Store that will be called at the start of a transaction.
addStartTransactionListener(listener: TransactionListener<Store>): string| Type | Description | |
|---|---|---|
listener | TransactionListener<Store> | The function that will be called at the start of a transaction. |
| returns | string | A unique |
The provided TransactionListener will receive a reference to the Store and two booleans to indicate whether Cell or Value data has been touched during the transaction. Since this is called at the start, they will both be false!
Note that a TransactionListener added to the Store with this method can mutate the Store, and its changes will be treated as part of the transaction that is starting.
Example
This example registers a listener that is called at start end of the transaction, just before its listeners will be called.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
})
.setValues({open: true, employees: 3});
const listenerId = store.addStartTransactionListener(() => {
console.log('Transaction started');
});
store.transaction(() =>
store.setCell('pets', 'fido', 'color', 'brown').setValue('employees', 3),
);
// -> 'Transaction started'
store.callListener(listenerId);
// -> 'Transaction started'
store.delListener(listenerId);
Since
v3.2.0
addWillFinishTransactionListener
The addWillFinishTransactionListener method registers a listener function with the Store that will be called just before other non-mutating listeners are called at the end of the transaction.
addWillFinishTransactionListener(listener: TransactionListener<Store>): string| Type | Description | |
|---|---|---|
listener | TransactionListener<Store> | The function that will be called before the end of a transaction. |
| returns | string | A unique |
This is useful if you need to know that a set of listeners are about to be called at the end of a transaction, perhaps to batch their consequences together.
The provided TransactionListener will receive a reference to the Store and two booleans to indicate whether Cell or Value data has been touched during the transaction. The two flags are intended as a hint about whether non-mutating listeners might be being called at the end of the transaction.
Here, 'touched' means that Cell or Value data has either been changed, or changed and then changed back to its original value during the transaction. The exception is a transaction that has been rolled back, for which the value of cellsTouched and valuesTouched in the listener will be false because all changes have been reverted.
Note that a TransactionListener added to the Store with this method can mutate the Store itself, and its changes will be treated as part of the transaction that is starting (and may fire non-mutating listeners after this).
Example
This example registers a listener that is called at the end of the transaction, just before its listeners will be called. The transactions shown here variously change, touch, and rollback cells, demonstrating how the cellsTouched and valuesTouched parameters in the listener work.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
})
.setValues({open: true, employees: 3});
const listenerId = store.addWillFinishTransactionListener((store) => {
const [cellsTouched, valuesTouched] = store.getTransactionLog() ?? {};
console.log(`Cells/Values touched: ${cellsTouched}/${valuesTouched}`);
});
const listenerId2 = store.addTablesListener(() =>
console.log('Tables changed'),
);
const listenerId3 = store.addValuesListener(() =>
console.log('Values changed'),
);
store.transaction(() =>
store.setCell('pets', 'fido', 'color', 'brown').setValue('employees', 3),
);
// -> 'Cells/Values touched: false/false'
store.transaction(() => store.setCell('pets', 'fido', 'color', 'walnut'));
// -> 'Cells/Values touched: true/false'
// -> 'Tables changed'
store.transaction(() => store.setValue('employees', 4));
// -> 'Cells/Values touched: false/true'
// -> 'Values changed'
store.transaction(() => {
store
.setRow('pets', 'felix', {species: 'cat'})
.delRow('pets', 'felix')
.setValue('city', 'London')
.delValue('city');
});
// -> 'Cells/Values touched: true/true'
// But no Tables or Values listeners fired since there are no net changes.
store.transaction(
() =>
store
.setRow('pets', 'felix', {species: 'cat'})
.setValue('city', 'London'),
() => true,
);
// -> 'Cells/Values touched: false/false'
// Transaction was rolled back.
store.callListener(listenerId);
// -> 'Cells/Values touched: false/false'
// It is meaningless to call this listener directly.
store
.delListener(listenerId)
.delListener(listenerId2)
.delListener(listenerId3);
Since
v1.3.0
callListener
The callListener method provides a way for you to manually provoke a listener to be called, even if the underlying data hasn't changed.
callListener(listenerId: string): thisThis is useful when you are using mutator listeners to guarantee that data conforms to programmatic conditions, and those conditions change such that you need to update the Store in bulk.
Examples
This example registers a listener that ensures a Cell has one of list of a valid values. After that list changes, the listener is called to apply the condition to the existing data.
import {createStore} from 'tinybase';
const validColors = ['walnut', 'brown', 'black'];
const store = createStore();
const listenerId = store.addCellListener(
'pets',
null,
'color',
(store, tableId, rowId, cellId, color) => {
if (!validColors.includes(color)) {
store.setCell(tableId, rowId, cellId, validColors[0]);
}
},
true,
);
store.setRow('pets', 'fido', {species: 'dog', color: 'honey'});
console.log(store.getRow('pets', 'fido'));
// -> {species: 'dog', color: 'walnut'}
validColors.shift();
console.log(validColors);
// -> ['brown', 'black']
store.callListener(listenerId);
console.log(store.getRow('pets', 'fido'));
// -> {species: 'dog', color: 'brown'}
store.delListener(listenerId);
This example registers a listener to Row Id changes. It is explicitly called and fires for two Tables in the Store.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
const listenerId = store.addRowIdsListener(null, (store, tableId) => {
console.log(`Row Ids listener called for ${tableId} table`);
});
store.callListener(listenerId);
// -> 'Row Ids listener called for pets table'
// -> 'Row Ids listener called for species table'
store.delListener(listenerId);
This example registers a listener Value changes. It is explicitly called and fires for two Values in the Store.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addValueListener(
null,
(store, valueId, value) => {
console.log(`Value listener called for ${valueId} value, ${value}`);
},
);
store.callListener(listenerId);
// -> 'Value listener called for open value, true'
// -> 'Value listener called for employees value, 3'
store.delListener(listenerId);
This example registers listeners for the end of transactions, and for invalid Cells. They are explicitly called, meaninglessly. The former receives empty arguments. The latter is not called at all.
import {createStore} from 'tinybase';
const store = createStore();
const listenerId = store.addWillFinishTransactionListener(
(store, cellsTouched, valuesTouched) => {
console.log(`Transaction finish: ${cellsTouched}/${valuesTouched}`);
},
);
store.callListener(listenerId);
// -> 'Transaction finish: undefined/undefined'
store.delListener(listenerId);
const listenerId2 = store.addInvalidCellListener(
null,
null,
null,
(store, tableId, rowId, cellId) => {
console.log('Invalid cell', tableId, rowId, cellId);
},
);
store.callListener(listenerId2);
// -> undefined
store.delListener(listenerId2);
Since
v1.0.0
delListener
The delListener method removes a listener that was previously added to the Store.
delListener(listenerId: string): this| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | this | A reference to the |
Use the Id returned by whichever method was used to add the listener. Note that the Store may re-use this Id for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTablesListener(() => {
console.log('Tables changed');
});
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Tables changed'
store.delListener(listenerId);
store.setCell('pets', 'fido', 'color', 'honey');
// -> undefined
// The listener is not called.
Since
v1.0.0
Iterator methods
forEachTable
The forEachTable method takes a function that it will then call for each Table in the Store.
forEachTable(tableCallback: TableCallback): void| Type | Description | |
|---|---|---|
tableCallback | TableCallback | The function that should be called for every |
| returns | void | This has no return value. |
This method is useful for iterating over the Table structure of the Store in a functional style. The tableCallback parameter is a TableCallback function that will be called with the Id of each Table, and with a function that can then be used to iterate over each Row of the Table, should you wish.
Example
This example iterates over each Table in a Store, and lists each Row Id within them.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
store.forEachTable((tableId, forEachRow) => {
console.log(tableId);
forEachRow((rowId) => console.log(`- ${rowId}`));
});
// -> 'pets'
// -> '- fido'
// -> 'species'
// -> '- dog'
Since
v1.0.0
forEachTableCell
The forEachTableCell method takes a function that it will then call for each Cell used across the whole Table.
forEachTableCell(
tableId: string,
tableCellCallback: TableCellCallback,
): void| Type | Description | |
|---|---|---|
tableId | string | |
tableCellCallback | TableCellCallback | The function that should be called for every |
| returns | void | This has no return value. |
This method is useful for iterating over the Cell structure of the Table in a functional style. The tableCellCallback parameter is a TableCellCallback function that will be called with the Id of each Cell and the count of Rows in the Table in which it appears.
Example
This example iterates over each Cell Id used across the whole Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}, felix: {species: 'cat', legs: 4}},
});
store.forEachTableCell('pets', (cellId, count) => {
console.log(`${cellId}: ${count}`);
});
// -> 'species: 2'
// -> 'legs: 1'
Since
v3.3.0
forEachRow
The forEachRow method takes a function that it will then call for each Row in a specified Table.
forEachRow(
tableId: string,
rowCallback: RowCallback,
): void| Type | Description | |
|---|---|---|
tableId | string | |
rowCallback | RowCallback | The function that should be called for every |
| returns | void | This has no return value. |
This method is useful for iterating over the Row structure of the Table in a functional style. The rowCallback parameter is a RowCallback function that will be called with the Id of each Row, and with a function that can then be used to iterate over each Cell of the Row, should you wish.
Example
This example iterates over each Row in a Table, and lists each Cell Id within them.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {color: 'black'},
},
});
store.forEachRow('pets', (rowId, forEachCell) => {
console.log(rowId);
forEachCell((cellId) => console.log(`- ${cellId}`));
});
// -> 'fido'
// -> '- species'
// -> 'felix'
// -> '- color'
Since
v1.0.0
forEachCell
The forEachCell method takes a function that it will then call for each Cell in a specified Row.
forEachCell(
tableId: string,
rowId: string,
cellCallback: CellCallback,
): void| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
cellCallback | CellCallback | The function that should be called for every |
| returns | void | This has no return value. |
This method is useful for iterating over the Cell structure of the Row in a functional style. The cellCallback parameter is a CellCallback function that will be called with the Id and value of each Cell.
Example
This example iterates over each Cell in a Row, and lists its value.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
store.forEachCell('pets', 'fido', (cellId, cell) => {
console.log(`${cellId}: ${cell}`);
});
// -> 'species: dog'
// -> 'color: brown'
Since
v1.0.0
forEachValue
The forEachValue method takes a function that it will then call for each Value in a Store.
forEachValue(valueCallback: ValueCallback): void| Type | Description | |
|---|---|---|
valueCallback | ValueCallback | The function that should be called for every |
| returns | void | This has no return value. |
This method is useful for iterating over the Value structure of the Store in a functional style. The valueCallback parameter is a ValueCallback function that will be called with the Id and value of each Value.
Example
This example iterates over each Value in a Store, and lists its value.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
store.forEachValue((valueId, value) => {
console.log(`${valueId}: ${value}`);
});
// -> 'open: true'
// -> 'employees: 3'
Since
v3.0.0
Transaction methods
finishTransaction
The finishTransaction method allows you to explicitly finish a transaction that has made multiple mutations to the Store, triggering all calls to the relevant listeners.
finishTransaction(doRollback?: DoRollback): this| Type | Description | |
|---|---|---|
doRollback? | DoRollback | An optional callback that should return |
| returns | this | A reference to the |
Transactions are useful for making bulk changes to the data in a Store, and when you don't want listeners to be called as you make each change. Changes are made silently during the transaction, and listeners relevant to the changes you have made will instead only be called when the whole transaction is complete.
Generally it is preferable to use the transaction method to wrap a block of code as a transaction. It simply calls both the startTransaction and finishTransaction methods for you. See that method for several transaction examples.
Use this finishTransaction method when you have a more 'open-ended' transaction, such as one containing mutations triggered from other events that are asynchronous or not occurring inline to your code. There must have been a corresponding startTransaction method that this completes, of course, otherwise this function has no effect.
The optional parameter, doRollback is a DoRollback callback that you can use to rollback the transaction if it did not complete to your satisfaction. It is called with getTransactionChanges and getTransactionLog parameters, which inform you of the net changes that have been made during the transaction, at different levels of detail. See the DoRollback documentation for more details.
Examples
This example makes changes to two Cells, first outside, and secondly within, a transaction that is explicitly started and finished. In the second case, the Row listener is only called once.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.addRowListener('pets', 'fido', () => console.log('Fido changed'));
store
.setCell('pets', 'fido', 'color', 'brown')
.setCell('pets', 'fido', 'sold', false);
// -> 'Fido changed'
// -> 'Fido changed'
store
.startTransaction()
.setCell('pets', 'fido', 'color', 'walnut')
.setCell('pets', 'fido', 'sold', true)
.finishTransaction();
// -> 'Fido changed'
This example makes multiple changes to the Store, including some attempts to update a Cell with invalid values. The doRollback callback receives information about the changes and invalid attempts, and then judges that the transaction should be rolled back to its original state.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({pets: {fido: {species: 'dog', color: 'brown'}}})
.setValues({open: true});
store
.startTransaction()
.setCell('pets', 'fido', 'color', 'black')
.setCell('pets', 'fido', 'eyes', ['left', 'right'])
.setCell('pets', 'fido', 'info', {sold: null})
.setValue('open', false)
.setValue('employees', ['alice', 'bob'])
.finishTransaction(() => {
const [, , changedCells, invalidCells, changedValues, invalidValues] =
store.getTransactionLog();
console.log(store.getTables());
console.log(changedCells);
console.log(invalidCells);
console.log(changedValues);
console.log(invalidValues);
return invalidCells['pets'] != null;
});
// -> {pets: {fido: {species: 'dog', color: 'black'}}}
// -> {pets: {fido: {color: ['brown', 'black']}}}
// -> {pets: {fido: {eyes: [['left', 'right']], info: [{sold: null}]}}}
// -> {open: [true, false]}
// -> {employees: [['alice', 'bob']]}
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'brown'}}}
console.log(store.getValues());
// -> {open: true}
Since
v1.3.0
getTransactionChanges
The getTransactionChanges method returns the net meaningful changes that have been made to a Store during a transaction.
getTransactionChanges(): ChangesThis is useful for deciding whether to rollback a transaction, for example. The returned object is only meaningful if the method is called when the Store is in a transaction - such as in a TransactionListener.
Example
This example makes changes to the Store. At the end of the transaction, detail about what changed is enumerated.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({pets: {fido: {species: 'dog', color: 'brown'}}})
.setValues({open: true});
store
.startTransaction()
.setCell('pets', 'fido', 'color', 'black')
.setValue('open', false)
.finishTransaction(() => {
const [changedCells, changedValues] = store.getTransactionChanges();
console.log(changedCells);
console.log(changedValues);
});
// -> {pets: {fido: {color: 'black'}}}
// -> {open: false}
Since
v5.0.0
getTransactionLog
The getTransactionLog method returns the changes that were made to a Store during a transaction in more detail, including invalid changes, and what previous values were.
getTransactionLog(): TransactionLog| returns | TransactionLog | A |
|---|
This is useful for deciding whether to rollback a transaction, for example. The returned object is only meaningful if the method is called when the Store is in a transaction - such as in a TransactionListener.
Example
This example makes changes to the Store. At the end of the transaction, detail about what changed is enumerated.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({pets: {fido: {species: 'dog', color: 'brown'}}})
.setValues({open: true});
store
.startTransaction()
.setCell('pets', 'fido', 'color', 'black')
.setCell('pets', 'fido', 'eyes', ['left', 'right'])
.setCell('pets', 'fido', 'info', {sold: null})
.setValue('open', false)
.setValue('employees', ['alice', 'bob'])
.finishTransaction(() => {
const [, , changedCells, invalidCells, changedValues, invalidValues] =
store.getTransactionLog();
console.log(changedCells);
console.log(invalidCells);
console.log(changedValues);
console.log(invalidValues);
});
// -> {pets: {fido: {color: ['brown', 'black']}}}
// -> {pets: {fido: {eyes: [['left', 'right']], info: [{sold: null}]}}}
// -> {open: [true, false]}
// -> {employees: [['alice', 'bob']]}
Since
v5.0.0
startTransaction
The startTransaction method allows you to explicitly start a transaction that will make multiple mutations to the Store, buffering all calls to the relevant listeners until it completes when you call the finishTransaction method.
startTransaction(): this| returns | this | A reference to the |
|---|
Transactions are useful for making bulk changes to the data in a Store, and when you don't want listeners to be called as you make each change. Changes are made silently during the transaction, and listeners relevant to the changes you have made will instead only be called when the whole transaction is complete.
Generally it is preferable to use the transaction method to wrap a block of code as a transaction. It simply calls both the startTransaction and finishTransaction methods for you. See that method for several transaction examples.
Use this startTransaction method when you have a more 'open-ended' transaction, such as one containing mutations triggered from other events that are asynchronous or not occurring inline to your code. You must remember to also call the finishTransaction method explicitly when it is done, of course.
Example
This example makes changes to two Cells, first outside, and secondly within, a transaction that is explicitly started and finished. In the second case, the Row listener is only called once.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.addRowListener('pets', 'fido', () => console.log('Fido changed'));
store
.setCell('pets', 'fido', 'color', 'brown')
.setCell('pets', 'fido', 'sold', false);
// -> 'Fido changed'
// -> 'Fido changed'
store
.startTransaction()
.setCell('pets', 'fido', 'color', 'walnut')
.setCell('pets', 'fido', 'sold', true)
.finishTransaction();
// -> 'Fido changed'
Since
v1.3.0
transaction
The transaction method takes a function that makes multiple mutations to the Store, buffering all calls to the relevant listeners until it completes.
transaction<Return>(
actions: () => Return,
doRollback?: DoRollback,
): Return| Type | Description | |
|---|---|---|
actions | () => Return | The function to be executed as a transaction. |
doRollback? | DoRollback | An optional callback that should return |
| returns | Return | Whatever value the provided transaction function returns. |
This method is useful for making bulk changes to the data in a Store, and when you don't want listeners to be called as you make each change. Changes are made silently during the transaction, and listeners relevant to the changes you have made will instead only be called when the whole transaction is complete.
If multiple changes are made to a piece of Store data throughout the transaction, a relevant listener will only be called with the final value (assuming it is different to the value at the start of the transaction), regardless of the changes that happened in between. For example, if a Cell had a value 'a' and then, within a transaction, it was changed to 'b' and then 'c', any CellListener registered for that cell would be called once as if there had been a single change from 'a' to 'c'.
Transactions can be nested. Relevant listeners will be called only when the outermost one completes.
The second, optional parameter, doRollback is a DoRollback callback that you can use to rollback the transaction if it did not complete to your satisfaction. See the DoRollback documentation for more details.
Examples
This example makes changes to two Cells, first outside, and secondly within, a transaction. In the second case, the Row listener is only called once.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.addRowListener('pets', 'fido', () => console.log('Fido changed'));
store
.setCell('pets', 'fido', 'color', 'brown')
.setCell('pets', 'fido', 'sold', false);
// -> 'Fido changed'
// -> 'Fido changed'
store.transaction(() =>
store
.setCell('pets', 'fido', 'color', 'walnut')
.setCell('pets', 'fido', 'sold', true),
);
// -> 'Fido changed'
This example makes multiple changes to one Cell. The Cell listener is called once - and with the final value - only if there is a net overall change.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.addCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId, newCell) => console.log(newCell),
);
store.transaction(() =>
store
.setCell('pets', 'fido', 'color', 'black')
.setCell('pets', 'fido', 'color', 'brown')
.setCell('pets', 'fido', 'color', 'walnut'),
);
// -> 'walnut'
store.transaction(() =>
store
.setCell('pets', 'fido', 'color', 'black')
.setCell('pets', 'fido', 'color', 'walnut'),
);
// -> undefined
// No net change during the transaction, so the listener is not called.
This example makes multiple changes to the Store, including some attempts to update a Cell and Value with invalid values. The doRollback callback receives information about the changes and invalid attempts, and then judges that the transaction should be rolled back to its original state.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({pets: {fido: {species: 'dog', color: 'brown'}}})
.setValues({open: true});
store.transaction(
() =>
store
.setCell('pets', 'fido', 'color', 'black')
.setCell('pets', 'fido', 'eyes', ['left', 'right'])
.setCell('pets', 'fido', 'info', {sold: null})
.setValue('open', false)
.setValue('employees', ['alice', 'bob']),
() => {
const [, , changedCells, invalidCells, changedValues, invalidValues] =
store.getTransactionLog();
console.log(store.getTables());
console.log(changedCells);
console.log(invalidCells);
console.log(changedValues);
console.log(invalidValues);
return invalidCells['pets'] != null;
},
);
// -> {pets: {fido: {species: 'dog', color: 'black'}}}
// -> {pets: {fido: {color: ['brown', 'black']}}}
// -> {pets: {fido: {eyes: [['left', 'right']], info: [{sold: null}]}}}
// -> {open: [true, false]}
// -> {employees: [['alice', 'bob']]}
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'brown'}}}
console.log(store.getValues());
// -> {open: true}
Since
v1.0.0
Deleter methods
delTables
The delTables method lets you remove all of the data in a Store.
delTables(): this| returns | this | A reference to the |
|---|
Example
This example removes the data of a Store.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.delTables();
console.log(store.getTables());
// -> {}
Since
v1.0.0
delTablesSchema
The delTablesSchema method lets you remove the TablesSchema of the Store.
delTablesSchema(): this| returns | this | A reference to the |
|---|
Example
This example removes the TablesSchema of a Store.
import {createStore} from 'tinybase';
const store = createStore().setTablesSchema({
pets: {species: {type: 'string'}},
});
store.delTablesSchema();
console.log(store.getTablesSchemaJson());
// -> '{}'
Since
v1.0.0
delTable
The delTable method lets you remove a single Table from the Store.
delTable(tableId: string): thisExample
This example removes a Table from a Store.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
store.delTable('pets');
console.log(store.getTables());
// -> {species: {dog: {price: 5}}}
Since
v1.0.0
delRow
The delRow method lets you remove a single Row from a Table.
delRow(
tableId: string,
rowId: string,
): this| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
| returns | this | A reference to the |
If this is the last Row in its Table, then that Table will be removed.
Example
This example removes a Row from a Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}, felix: {species: 'cat'}},
});
store.delRow('pets', 'fido');
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
Since
v1.0.0
delCell
The delCell method lets you remove a single Cell from a Row.
delCell(
tableId: string,
rowId: string,
cellId: string,
forceDel?: boolean,
): this| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
forceDel? | boolean | An optional flag to indicate that the whole |
| returns | this | A reference to the |
When there is no TablesSchema applied to the Store, then if this is the last Cell in its Row, then that Row will be removed. If, in turn, that is the last Row in its Table, then that Table will be removed.
If there is a TablesSchema applied to the Store and it specifies a default value for this Cell, then deletion will result in it being set back to its default value. To override this, use the forceDel parameter, as described below.
The forceDel parameter is an optional flag that is only relevant if a TablesSchema provides a default value for this Cell. Under such circumstances, deleting a Cell value will normally restore it to the default value. If this flag is set to true, the complete removal of the Cell is instead guaranteed. But since doing do so would result in an invalid Row (according to the TablesSchema), in fact the whole Row is deleted to retain the integrity of the Table. Therefore, this flag should be used with caution.
Examples
This example removes a Cell from a Row without a TablesSchema.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', sold: true}},
});
store.delCell('pets', 'fido', 'sold');
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
This example removes a Cell from a Row with a TablesSchema that defaults its value.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({
pets: {fido: {species: 'dog', sold: true}},
})
.setTablesSchema({
pets: {
species: {type: 'string'},
sold: {type: 'boolean', default: false},
},
});
store.delCell('pets', 'fido', 'sold');
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', sold: false}}}
This example removes a Cell from a Row with a TablesSchema that defaults its value, but uses the forceDel parameter to override it.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({
pets: {fido: {species: 'dog', sold: true}, felix: {species: 'cat'}},
})
.setTablesSchema({
pets: {
species: {type: 'string'},
sold: {type: 'boolean', default: false},
},
});
store.delCell('pets', 'fido', 'sold', true);
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat', sold: false}}}
Since
v1.0.0
delValues
The delValues method lets you remove all the Values from a Store.
delValues(): this| returns | this | A reference to the |
|---|
If there is a ValuesSchema applied to the Store and it specifies a default value for any Value Id, then deletion will result in it being set back to its default value.
Examples
This example removes all Values from a Store without a ValuesSchema.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
store.delValues();
console.log(store.getValues());
// -> {}
This example removes all Values from a Store with a ValuesSchema that defaults one of its values.
import {createStore} from 'tinybase';
const store = createStore()
.setValues({open: true, employees: 3})
.setValuesSchema({
open: {type: 'boolean', default: false},
employees: {type: 'number'},
});
store.delValues();
console.log(store.getValues());
// -> {open: false}
Since
v3.0.0
delValuesSchema
The delValuesSchema method lets you remove the ValuesSchema of the Store.
delValuesSchema(): this| returns | this | A reference to the |
|---|
Example
This example removes the ValuesSchema of a Store.
import {createStore} from 'tinybase';
const store = createStore().setValuesSchema({
sold: {type: 'boolean', default: false},
});
store.delValuesSchema();
console.log(store.getValuesSchemaJson());
// -> '{}'
Since
v3.0.0
delValue
The delValue method lets you remove a single Value from a Store.
delValue(valueId: string): thisIf there is a ValuesSchema applied to the Store and it specifies a default value for this Value Id, then deletion will result in it being set back to its default value.
Examples
This example removes a Value from a Store without a ValuesSchema.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
store.delValue('employees');
console.log(store.getValues());
// -> {open: true}
This example removes a Value from a Store with a ValuesSchema that defaults its value.
import {createStore} from 'tinybase';
const store = createStore()
.setValues({open: true, employees: 3})
.setValuesSchema({
open: {type: 'boolean', default: false},
employees: {type: 'number'},
});
store.delValue('open');
console.log(store.getValues());
// -> {open: false, employees: 3}
Since
v3.0.0
delSchema
The delSchema method lets you remove both the TablesSchema and ValuesSchema of the Store.
delSchema(): this| returns | this | A reference to the |
|---|
Prior to v3.0, this method removed the TablesSchema only.
Example
This example removes the TablesSchema and ValuesSchema of a Store.
import {createStore} from 'tinybase';
const store = createStore()
.setTablesSchema({
pets: {species: {type: 'string'}},
})
.setValuesSchema({
sold: {type: 'boolean', default: false},
});
store.delSchema();
console.log(store.getSchemaJson());
// -> '[{},{}]'
Since
v3.0.0
Development methods
getListenerStats
The getListenerStats method provides a set of statistics about the listeners registered with the Store, and is used for debugging purposes.
getListenerStats(): StoreListenerStats| returns | StoreListenerStats | A |
|---|
The StoreListenerStats object contains a breakdown of the different types of listener. Totals include both mutator and non-mutator listeners.
The method is intended to be used during development to ensure your application is not leaking listener registrations, for example.
Example
This example gets the listener statistics of a small and simple Store.
import {createStore} from 'tinybase';
const store = createStore();
store.addTablesListener(() => console.log('Tables changed'));
store.addRowIdsListener(() => console.log('Row Ids changed'));
const listenerStats = store.getListenerStats();
console.log(listenerStats.rowIds);
// -> 1
console.log(listenerStats.tables);
// -> 1
Since
v1.0.0
Functions
createStore
The createStore function creates a Store, and is the main entry point into the store module.
createStore(): StoreSince (or perhaps because) it is the most important function in the whole module, it is trivially simple.
Examples
This example creates a Store.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getTables());
// -> {}
This example creates a Store with some initial data:
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
This example creates a Store with some initial data and a TablesSchema:
import {createStore} from 'tinybase';
const store = createStore()
.setTables({pets: {fido: {species: 'dog'}}})
.setTablesSchema({
pets: {
species: {type: 'string'},
sold: {type: 'boolean', default: false},
},
});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', sold: false}}}
See also
The Basics guides
Since
v1.0.0
Type Aliases
Listener type aliases
TablesListener
The TablesListener type describes a function that is used to listen to changes to the tabular part of the Store.
(
store: Store,
getCellChange: GetCellChange | undefined,
): void| Type | Description | |
|---|---|---|
store | Store | A reference to the |
getCellChange | GetCellChange | undefined | A function that returns information about any |
| returns | void | This has no return value. |
A TablesListener is provided when using the addTablesListener method. See that method for specific examples.
When called, a TablesListener is given a reference to the Store and a GetCellChange function that can be used to query Cell values before and after the current transaction.
Note that if the listener was manually forced to be called (with the callListener method rather than due to a real change in the Store), the GetCellChange function will not be present.
Since
v1.0.0
HasTablesListener
The HasTablesListener type describes a function that is used to listen to Tables as a whole being added to or removed from the Store.
(
store: Store,
hasTables: boolean,
): void| Type | Description | |
|---|---|---|
store | Store | A reference to the |
hasTables | boolean | Whether |
| returns | void | This has no return value. |
A HasTablesListener is provided when using the addHasTablesListener method. See that method for specific examples.
When called, a HasTablesListener is given a reference to the Store. It is also given a flag to indicate whether Tables now exist (having not done previously), or do not (having done so previously).
Since
v4.4.0
TableIdsListener
The TableIdsListener type describes a function that is used to listen to changes to the Table Ids in the Store.
(
store: Store,
getIdChanges: GetIdChanges | undefined,
): void| Type | Description | |
|---|---|---|
store | Store | A reference to the |
getIdChanges | GetIdChanges | undefined | A function that returns information about the |
| returns | void | This has no return value. |
A TableIdsListener is provided when using the addTableIdsListener method. See that method for specific examples.
When called, a TableIdsListener is given a reference to the Store.
Since v3.3, the listener is also passed a GetIdChanges function that can be used to query which Ids changed during the transaction.
Since
v1.0.0
TableCellIdsListener
The TableCellIdsListener type describes a function that is used to listen to changes to the Cell Ids that appear anywhere in a Table.
(
store: Store,
tableId: Id,
getIdChanges: GetIdChanges | undefined,
): void| Type | Description | |
|---|---|---|
store | Store | A reference to the |
tableId | Id | |
getIdChanges | GetIdChanges | undefined | A function that returns information about the |
| returns | void | This has no return value. |
A TableCellIdsListener is provided when using the addTableCellIdsListener method. See that method for specific examples.
When called, a TableCellIdsListener is given a reference to the Store, the Id of the Table whose Cell Ids changed, and a GetIdChanges function that can be used to query which Ids changed during the transaction.
Since
v3.3.0
TableListener
The TableListener type describes a function that is used to listen to changes to a Table.
(
store: Store,
tableId: Id,
getCellChange: GetCellChange | undefined,
): void| Type | Description | |
|---|---|---|
store | Store | A reference to the |
tableId | Id | |
getCellChange | GetCellChange | undefined | A function that returns information about any |
| returns | void | This has no return value. |
A TableListener is provided when using the addTableListener method. See that method for specific examples.
When called, a TableListener is given a reference to the Store, the Id of the Table that changed, and a GetCellChange function that can be used to query Cell values before and after the current transaction.
Note that if the listener was manually forced to be called (with the callListener method rather than due to a real change in the Store), the GetCellChange function will not be present.
Since
v1.0.0
HasTableCellListener
The HasTableCellListener type describes a function that is used to listen to a Cell being added to or removed from a Table as a whole.
(
store: Store,
tableId: Id,
cellId: Id,
hasTableCell: boolean,
): void| Type | Description | |
|---|---|---|
store | Store | A reference to the |
tableId | Id | |
cellId | Id | |
hasTableCell | boolean | |
| returns | void | This has no return value. |
A HasTableCellListener is provided when using the addHasTableCellListener method. See that method for specific examples.
When called, a HasTableCellListener is given a reference to the Store, the Id of the Table that changed, and the Id of the Cell that changed. It is also given a flag to indicate whether the Cell now exists anywhere in the Table (having not done previously), or does not (having done so previously).
Since
v4.4.0
HasTableListener
The HasTableListener type describes a function that is used to listen to a Table being added to or removed from the Store.
(
store: Store,
tableId: Id,
hasTable: boolean,
): void| Type | Description | |
|---|---|---|
store | Store | A reference to the |
tableId | Id | |
hasTable | boolean | Whether the |
| returns | void | This has no return value. |
A HasTableListener is provided when using the addHasTableListener method. See that method for specific examples.
When called, a HasTableListener is given a reference to the Store, and the Id of the Table that changed. It is also given a flag to indicate whether the Table now exists (having not done previously), or does not (having done so previously).
Since
v4.4.0
RowIdsListener
The RowIdsListener type describes a function that is used to listen to changes to the Row Ids in a Table.
(
store: Store,
tableId: Id,
getIdChanges: GetIdChanges | undefined,
): void| Type | Description | |
|---|---|---|
store | Store | A reference to the |
tableId | Id | |
getIdChanges | GetIdChanges | undefined | A function that returns information about the |
| returns | void | This has no return value. |
A RowIdsListener is provided when using the addRowIdsListener method. See that method for specific examples.
When called, a RowIdsListener is given a reference to the Store, and the Id of the Table whose Row Ids changed.
Since v3.3, the listener is also passed a GetIdChanges function that can be used to query which Ids changed during the transaction.
Since
v1.0.0
SortedRowIdsListener
The SortedRowIdsListener type describes a function that is used to listen to changes to sorted Row Ids in a Table.
(
store: Store,
tableId: Id,
cellId: Id | undefined,
descending: boolean,
offset: number,
limit: number | undefined,
sortedRowIds: Ids,
): void| Type | Description | |
|---|---|---|
store | Store | A reference to the |
tableId | Id | |
cellId | Id | undefined | |
descending | boolean | Whether the sorting was in descending order. |
offset | number | |
limit | number | undefined | |
sortedRowIds | Ids | |
| returns | void | This has no return value. |
A SortedRowIdsListener is provided when using the addSortedRowIdsListener method. See that method for specific examples.
When called, a SortedRowIdsListener is given a reference to the Store, the Id of the Table whose Row Ids sorting changed, the Cell Id being used to sort them, whether descending or not, and the offset and limit of the number of Ids returned, for pagination purposes. It also receives the sorted array of Ids itself, so that you can use them in the listener without the additional cost of an explicit call to getSortedRowIds.
Since
v2.0.0
HasRowListener
The HasRowListener type describes a function that is used to listen to a Row being added to or removed from the Store.
(
store: Store,
tableId: Id,
rowId: Id,
hasRow: boolean,
): void| Type | Description | |
|---|---|---|
store | Store | A reference to the |
tableId | Id | |
rowId | Id | |
hasRow | boolean | Whether the |
| returns | void | This has no return value. |
A HasRowListener is provided when using the addHasRowListener method. See that method for specific examples.
When called, a HasRowListener is given a reference to the Store, the Id of the Table that changed, and the Id of the Row that changed. It is also given a flag to indicate whether the Row now exists (having not done previously), or does not (having done so previously).
Since
v4.4.0
RowCountListener
The RowCountListener type describes a function that is used to listen to changes to the number of Row objects in a Table.
(
store: Store,
tableId: Id,
count: number,
): void| Type | Description | |
|---|---|---|
store | Store | A reference to the |
tableId | Id | |
count | number | |
| returns | void | This has no return value. |
A RowCountListener is provided when using the addRowCountListener method. See that method for specific examples.
When called, a RowCountListener is given a reference to the Store, the Id of the Table whose Row Ids changed, and the number of Row objects in the Table.
Since
v4.1.0
RowListener
The RowListener type describes a function that is used to listen to changes to a Row.
(
store: Store,
tableId: Id,
rowId: Id,
getCellChange: GetCellChange | undefined,
): void| Type | Description | |
|---|---|---|
store | Store | A reference to the |
tableId | Id | |
rowId | Id | |
getCellChange | GetCellChange | undefined | A function that returns information about any |
| returns | void | This has no return value. |
A RowListener is provided when using the addRowListener method. See that method for specific examples.
When called, a RowListener is given a reference to the Store, the Id of the Table that changed, the Id of the Row that changed, and a GetCellChange function that can be used to query Cell values before and after the current transaction.
Note that if the listener was manually forced to be called (with the callListener method rather than due to a real change in the Store), the GetCellChange function will not be present.
Since
v1.0.0
CellIdsListener
The CellIdsListener type describes a function that is used to listen to changes to the Cell Ids in a Row.
(
store: Store,
tableId: Id,
rowId: Id,
getIdChanges: GetIdChanges | undefined,
): void| Type | Description | |
|---|---|---|
store | Store | A reference to the |
tableId | Id | |
rowId | Id | |
getIdChanges | GetIdChanges | undefined | A function that returns information about the |
| returns | void | This has no return value. |
A CellIdsListener is provided when using the addCellIdsListener method. See that method for specific examples.
When called, a CellIdsListener is given a reference to the Store, the Id of the Table that changed, and the Id of the Row whose Cell Ids changed.
Since v3.3, the listener is also passed a GetIdChanges function that can be used to query which Ids changed during the transaction.
Since
v1.0.0
CellChange
The CellChange type describes a Cell's changes during a transaction.
[changed: boolean, oldCell: CellOrUndefined, newCell: CellOrUndefined]This is returned by the GetCellChange function that is provided to every listener when called. This array contains the previous value of a Cell before the current transaction, the new value after it, and a convenience flag that indicates that the value has changed.
Since
v1.0.0
CellListener
The CellListener type describes a function that is used to listen to changes to a Cell.
(
store: Store,
tableId: Id,
rowId: Id,
cellId: Id,
newCell: Cell,
oldCell: Cell,
getCellChange: GetCellChange | undefined,
): void| Type | Description | |
|---|---|---|
store | Store | A reference to the |
tableId | Id | |
rowId | Id | |
cellId | Id | |
newCell | Cell | The new value of the |
oldCell | Cell | The old value of the |
getCellChange | GetCellChange | undefined | A function that returns information about any |
| returns | void | This has no return value. |
A CellListener is provided when using the addCellListener method. See that method for specific examples.
When called, a CellListener is given a reference to the Store, the Id of the Table that changed, the Id of the Row that changed, and the Id of Cell that changed. It is also given the new value of the Cell, the old value of the Cell, and a GetCellChange function that can be used to query Cell values before and after the current transaction.
Note that if the listener was manually forced to be called (with the callListener method rather than due to a real change in the Store), the GetCellChange function will not be present and the new and old values of the Cell will be the same.
Since
v1.0.0
GetCellChange
The GetCellChange type describes a function that returns information about any Cell's changes during a transaction.
(
tableId: Id,
rowId: Id,
cellId: Id,
): CellChange| Type | Description | |
|---|---|---|
tableId | Id | |
rowId | Id | |
cellId | Id | |
| returns | CellChange | A |
A GetCellChange function is provided to every listener when called due the Store changing. The listener can then fetch the previous value of a Cell before the current transaction, the new value after it, and a convenience flag that indicates that the value has changed.
Since
v1.0.0
HasCellListener
The HasCellListener type describes a function that is used to listen to a Cell being added to or removed from the Store.
(
store: Store,
tableId: Id,
rowId: Id,
cellId: Id,
hasCell: boolean,
): void| Type | Description | |
|---|---|---|
store | Store | A reference to the |
tableId | Id | |
rowId | Id | |
cellId | Id | |
hasCell | boolean | Whether the |
| returns | void | This has no return value. |
A HasCellListener is provided when using the addHasCellListener method. See that method for specific examples.
When called, a HasCellListener is given a reference to the Store, the Id of the Table that changed, the Id of the Row that changed, and the Id of Cell that changed. It is also given a flag to indicate whether the Cell now exists (having not done previously), or does not (having done so previously).
Since
v4.4.0
InvalidCellListener
The InvalidCellListener type describes a function that is used to listen to attempts to set invalid data to a Cell.
(
store: Store,
tableId: Id,
rowId: Id,
cellId: Id,
invalidCells: any[],
): void| Type | Description | |
|---|---|---|
store | Store | A reference to the |
tableId | Id | |
rowId | Id | |
cellId | Id | |
invalidCells | any[] | An array of the values of the |
| returns | void | This has no return value. |
An InvalidCellListener is provided when using the addInvalidCellListener method. See that method for specific examples.
When called, an InvalidCellListener is given a reference to the Store, the Id of the Table, the Id of the Row, and the Id of Cell that was being attempted to be changed. It is also given the invalid value of the Cell, which could have been of absolutely any type. Since there could have been multiple failed attempts to set the Cell within a single transaction, this is an array containing each attempt, chronologically.
Since
v1.1.0
ValuesListener
The ValuesListener type describes a function that is used to listen to changes to all the Values in a Store.
(
store: Store,
getValueChange: GetValueChange | undefined,
): void| Type | Description | |
|---|---|---|
store | Store | A reference to the |
getValueChange | GetValueChange | undefined | A function that returns information about any |
| returns | void | This has no return value. |
A ValuesListener is provided when using the addValuesListener method. See that method for specific examples.
When called, a ValuesListener is given a reference to the Store and a GetValueChange function that can be used to query Values before and after the current transaction.
Note that if the listener was manually forced to be called (with the callListener method rather than due to a real change in the Store), the GetValueChange function will not be present.
Since
v1.0.0
HasValuesListener
The HasValuesListener type describes a function that is used to listen to Values as a whole being added to or removed from the Store.
(
store: Store,
hasValues: boolean,
): void| Type | Description | |
|---|---|---|
store | Store | A reference to the |
hasValues | boolean | Whether |
| returns | void | This has no return value. |
A HasValuesListener is provided when using the addHasValuesListener method. See that method for specific examples.
When called, a HasValuesListener is given a reference to the Store. It is also given a flag to indicate whether Values now exist (having not done previously), or do not (having done so previously).
Since
v4.4.0
GetValueChange
The GetValueChange type describes a function that returns information about any Value's changes during a transaction.
(valueId: Id): ValueChange| Type | Description | |
|---|---|---|
valueId | Id | |
| returns | ValueChange | A |
A GetValueChange function is provided to every listener when called due the Store changing. The listener can then fetch the previous value of a Value before the current transaction, the new value after it, and a convenience flag that indicates that the value has changed.
Since
v1.0.0
HasValueListener
The HasValueListener type describes a function that is used to listen to a Value being added to or removed from the Store.
(
store: Store,
valueId: Id,
hasValue: boolean,
): void| Type | Description | |
|---|---|---|
store | Store | A reference to the |
valueId | Id | |
hasValue | boolean | Whether the |
| returns | void | This has no return value. |
A HasValueListener is provided when using the addHasValueListener method. See that method for specific examples.
When called, a HasValueListener is given a reference to the Store and the Id of Value that changed. It is also given a flag to indicate whether the Value now exists (having not done previously), or does not (having done so previously).
Since
v4.4.0
InvalidValueListener
The InvalidValueListener type describes a function that is used to listen to attempts to set invalid data to a Value.
(
store: Store,
valueId: Id,
invalidValues: any[],
): void| Type | Description | |
|---|---|---|
store | Store | A reference to the |
valueId | Id | |
invalidValues | any[] | An array of the |
| returns | void | This has no return value. |
An InvalidValueListener is provided when using the addInvalidValueListener method. See that method for specific examples.
When called, an InvalidValueListener is given a reference to the Store and the Id of Value that was being attempted to be changed. It is also given the invalid value of the Value, which could have been of absolutely any type. Since there could have been multiple failed attempts to set the Value within a single transaction, this is an array containing each attempt, chronologically.
Since
v3.0.0
ValueChange
The ValueChange type describes a Value's changes during a transaction.
[changed: boolean, oldValue: ValueOrUndefined, newValue: ValueOrUndefined]This is returned by the GetValueChange function that is provided to every listener when called. This array contains the previous value of a Value before the current transaction, the new value after it, and a convenience flag that indicates that the value has changed.
Since
v1.0.0
ValueIdsListener
The ValueIdsListener type describes a function that is used to listen to changes to the Value Ids in a Store.
(
store: Store,
getIdChanges: GetIdChanges | undefined,
): void| Type | Description | |
|---|---|---|
store | Store | A reference to the |
getIdChanges | GetIdChanges | undefined | A function that returns information about the |
| returns | void | This has no return value. |
A ValueIdsListener is provided when using the addValueIdsListener method. See that method for specific examples.
When called, a ValueIdsListener is given a reference to the Store.
Since v3.3, the listener is also passed a GetIdChanges function that can be used to query which Ids changed during the transaction.
Since
v1.0.0
ValueListener
The ValueListener type describes a function that is used to listen to changes to a Value.
(
store: Store,
valueId: Id,
newValue: Value,
oldValue: Value,
getValueChange: GetValueChange | undefined,
): void| Type | Description | |
|---|---|---|
store | Store | A reference to the |
valueId | Id | |
newValue | Value | The new value of the |
oldValue | Value | The old value of the |
getValueChange | GetValueChange | undefined | A function that returns information about any |
| returns | void | This has no return value. |
A ValueListener is provided when using the addValueListener method. See that method for specific examples.
When called, a ValueListener is given a reference to the Store and the Id of Value that changed. It is also given the new value of the Value, the old value of the Value, and a GetValueChange function that can be used to query Values before and after the current transaction.
Note that if the listener was manually forced to be called (with the callListener method rather than due to a real change in the Store), the GetValueChange function will not be present and the new and old values of the Value will be the same.
Since
v3.0.0
GetIdChanges
The GetIdChanges type describes a function that returns information about the changes to a set of Ids during a transaction.
(): {[id: Id]: 1 | -1}| returns | {[id: Id]: 1 | -1} | An object keyed by |
|---|
A GetIdChanges function is provided to every listener when called due Ids in the Store changing. It returns an object where each key is an Id that changed. The listener can then easily identify which Ids have been added (those with the value 1), and which have been removed (those with the value -1).
Since
v3.3.0
TransactionListener
The TransactionListener type describes a function that is used to listen to the completion of a transaction for the Store.
(store: Store): void| Type | Description | |
|---|---|---|
store | Store | A reference to the |
| returns | void | This has no return value. |
A TransactionListener is provided when using the addWillFinishTransactionListener and addDidFinishTransactionListener methods. See those methods for specific examples.
Since v5.0, this listener is called with no arguments other than the Store. You can use the getTransactionChanges method and getTransactionLog method of the Store directly to get information about the changes made within the transaction.
Since
v1.0.0
Callback type aliases
TableCallback
The TableCallback type describes a function that takes a Table's Id and a callback to loop over each Row within it.
(
tableId: Id,
forEachRow: (rowCallback: RowCallback) => void,
): void| Type | Description | |
|---|---|---|
tableId | Id | |
forEachRow | (rowCallback: RowCallback) => void | A function that will let you iterate over the |
| returns | void | This has no return value. |
A TableCallback is provided when using the forEachTable method, so that you can do something based on every Table in the Store. See that method for specific examples.
Since
v1.0.0
TableCellCallback
The TableCellCallback type describes a function that takes a Cell's Id and the count of times it appears across a whole Table.
(
cellId: Id,
count: number,
): void| Type | Description | |
|---|---|---|
cellId | Id | |
count | number | |
| returns | void | This has no return value. |
A TableCellCallback is provided when using the forEachTableCell method, so that you can do something based on every Cell used across a Table. See that method for specific examples.
Since
v1.0.0
RowCallback
The RowCallback type describes a function that takes a Row's Id and a callback to loop over each Cell within it.
(
rowId: Id,
forEachCell: (cellCallback: CellCallback) => void,
): void| Type | Description | |
|---|---|---|
rowId | Id | |
forEachCell | (cellCallback: CellCallback) => void | A function that will let you iterate over the |
| returns | void | This has no return value. |
A RowCallback is provided when using the forEachRow method, so that you can do something based on every Row in a Table. See that method for specific examples.
Since
v1.0.0
CellCallback
The CellCallback type describes a function that takes a Cell's Id and its value.
(
cellId: Id,
cell: Cell,
): voidA CellCallback is provided when using the forEachCell method, so that you can do something based on every Cell in a Row. See that method for specific examples.
Since
v1.0.0
GetCell
The GetCell type describes a function that takes a Id and returns the Cell value for a particular Row.
(cellId: Id): CellOrUndefined| Type | Description | |
|---|---|---|
cellId | Id | |
| returns | CellOrUndefined |
A GetCell can be provided when setting definitions, as in the setMetricDefinition method of a Metrics object, or the setIndexDefinition method of an Indexes object. See those methods for specific examples.
Since
v1.0.0
MapCell
The MapCell type describes a function that takes an existing Cell value and returns another.
(cell: CellOrUndefined): Cell| Type | Description | |
|---|---|---|
cell | CellOrUndefined | The current value of the |
| returns | Cell |
A MapCell can be provided in the setCell method to map an existing value to a new one, such as when incrementing a number. See that method for specific examples.
Since
v1.0.0
MapValue
The MapValue type describes a function that takes an existing Value and returns another.
(value: ValueOrUndefined): Value| Type | Description | |
|---|---|---|
value | ValueOrUndefined | |
| returns | Value |
A MapValue can be provided in the setValue method to map an existing Value to a new one, such as when incrementing a number. See that method for specific examples.
Since
v3.0.0
ValueCallback
The ValueCallback type describes a function that takes a Value's Id and its actual value.
(
valueId: Id,
value: Value,
): voidA ValueCallback is provided when using the forEachValue method, so that you can do something based on every Value in a Store. See that method for specific examples.
Since
v3.0.0
DoRollback
The DoRollback type describes a function that you can use to rollback the transaction if it did not complete to your satisfaction.
(store: Store): booleanA DoRollback can be provided when calling the transaction method or the finishTransaction method. See those methods for specific examples.
Since v5.0, this function is called with the Store as a single argument. You can use the getTransactionChanges method and getTransactionLog method of the Store directly to decide whether to do the rollback.
Since
v1.0.0
Schema type aliases
TablesSchema
The TablesSchema type describes the tabular structure of a Store in terms of valid Table Ids and the types of Cell that can exist within them.
{[tableId: Id]: {[cellId: Id]: CellSchema}}A TablesSchema comprises a JavaScript object describing each Table, in turn a nested JavaScript object containing information about each Cell and its CellSchema. It is provided to the setTablesSchema method.
Example
When applied to a Store, this TablesSchema only allows one Table called pets, in which each Row may contain a string species Cell, and is guaranteed to contain a boolean sold Cell that defaults to false.
import type {TablesSchema} from 'tinybase';
export const tableSchema: TablesSchema = {
pets: {
species: {type: 'string'},
sold: {type: 'boolean', default: false},
},
};
Since
v1.0.0
NoTablesSchema
The NoTablesSchema type is a TablesSchema-like type for when one has not been provided.
{[tableId: Id]: {[cellId: Id]: {type: "any"}}}Since
v1.0.0
OptionalTablesSchema
The OptionalTablesSchema type is used by generic types that can optionally take a TablesSchema.
TablesSchema | NoTablesSchemaSince
v1.0.0
CellSchema
The CellSchema type describes what values are allowed for each Cell in a Table.
{
type: "string";
default?: string;
} | {
type: "number";
default?: number;
} | {
type: "boolean";
default?: boolean;
}A CellSchema specifies the type of the Cell (string, boolean, or number), and what the default value can be when an explicit value is not specified.
If a default value is provided (and its type is correct), you can be certain that that Cell will always be present in a Row.
If the default value is not provided (or its type is incorrect), the Cell may be missing from the Row, but when present you can be guaranteed it is of the correct type.
Example
When applied to a Store, this CellSchema ensures a boolean Cell is always present, and defaults it to false.
import type {CellSchema} from 'tinybase';
export const requiredBoolean: CellSchema = {
type: 'boolean',
default: false,
};
Since
v1.0.0
ValuesSchema
The ValuesSchema type describes the keyed Values that can be set in a Store and their types.
{[valueId: Id]: ValueSchema}A ValuesSchema comprises a JavaScript object describing each Value and its ValueSchema. It is provided to the setValuesSchema method.
Example
When applied to a Store, this ValuesSchema only allows one boolean Value called open, that defaults to false.
import type {ValuesSchema} from 'tinybase';
export const valuesSchema: ValuesSchema = {
open: {type: 'boolean', default: false},
};
Since
v3.0.0
NoValuesSchema
The NoValuesSchema type is a ValuesSchema-like type for when one has not been provided.
{[valueId: Id]: {type: "any"}}Since
v1.0.0
OptionalValuesSchema
The OptionalValuesSchema type is used by generic types that can optionally take a ValuesSchema.
ValuesSchema | NoValuesSchemaSince
v1.0.0
ValueSchema
The ValueSchema type describes what values are allowed for keyed Values in a Store.
{
type: "string";
default?: string;
} | {
type: "number";
default?: number;
} | {
type: "boolean";
default?: boolean;
}A ValueSchema specifies the type of the Value (string, boolean, or number), and what the default value can be when an explicit value is not specified.
If a default value is provided (and its type is correct), you can be certain that the Value will always be present in a Store.
If the default value is not provided (or its type is incorrect), the Value may not be present in the Store, but when present you can be guaranteed it is of the correct type.
Example
When applied to a Store, this ValueSchema ensures a boolean Value is always present, and defaults it to false.
import type {ValueSchema} from 'tinybase';
export const requiredBoolean: ValueSchema = {
type: 'boolean',
default: false,
};
Since
v3.0.0
NoSchemas
The NoSchemas type is used as a default by generic types that can optionally take either or both of a TablesSchema and ValuesSchema.
[NoTablesSchema, NoValuesSchema]Since
v1.0.0
OptionalSchemas
The OptionalSchemas type is used by generic types that can optionally take either or both of a TablesSchema and ValuesSchema.
[OptionalTablesSchema, OptionalValuesSchema]Since
v1.0.0
Store type aliases
Tables
The Tables type is the data structure representing all of the data in a Store.
{[tableId: Id]: Table}A Tables object is used when setting all of the tables together with the setTables method, and when getting them back out again with the getTables method. A Tables object is a regular JavaScript object containing individual Table objects, keyed by their Id.
Example
import type {Tables} from 'tinybase';
export const tables: Tables = {
pets: {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat'},
},
species: {
dog: {price: 5},
cat: {price: 4},
},
};
Since
v1.0.0
Table
The Table type is the data structure representing the data in a single table.
{[rowId: Id]: Row}A Table is used when setting a table with the setTable method, and when getting it back out again with the getTable method. A Table object is a regular JavaScript object containing individual Row objects, keyed by their Id.
Example
import type {Table} from 'tinybase';
export const table: Table = {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat'},
};
Since
v1.0.0
SortedRowIdsArgs
The SortedRowIdsArgs type describes the arguments used to sort Row Ids using the getSortedRowIds method.
{
tableId: Id;
cellId?: Id;
descending?: boolean;
offset?: number;
limit?: number;
}| Type | Description | |
|---|---|---|
tableId | Id | |
cellId? | Id | The optional |
descending? | boolean | Whether the sorting should be in descending order, defaulting to false. |
offset? | number | The number of |
limit? | number |
It's an object containing the Id of the Table in the Store, and optional cellId, descending, offset, and limit parameters. See the getSortedRowIds method for specific examples.
Since
v6.1.0
Row
The Row type is the data structure representing the data in a single row.
{[cellId: Id]: Cell}A Row is used when setting a row with the setRow method, and when getting it back out again with the getRow method. A Row object is a regular JavaScript object containing individual Cell objects, keyed by their Id.
Example
import type {Row} from 'tinybase';
export const row: Row = {species: 'dog', color: 'brown'};
Since
v1.0.0
Cell
The Cell type is the data structure representing the data in a single cell.
string | number | booleanA Cell is used when setting a cell with the setCell method, and when getting it back out again with the getCell method. A Cell is a JavaScript string, number, or boolean.
Example
import type {Cell} from 'tinybase';
export const cell: Cell = 'dog';
Since
v1.0.0
CellOrUndefined
The CellOrUndefined type is a data structure representing the data in a single cell, or the value undefined.
Cell | undefinedThis is used when describing a Cell that is present or that is not present, such as when it has been deleted, or when describing a previous state where the Cell value has since been added.
Since
v1.0.0
Values
The Values type is the data structure representing all the keyed values in a Store.
{[valueId: Id]: Value}A Values object is used when setting values with the setValues method, and when getting them back out again with the getValues method. A Values object is a regular JavaScript object containing individual Value objects, keyed by their Id.
Example
import type {Values} from 'tinybase';
export const values: Values = {open: true, employees: 4};
Since
v3.0.0
Value
The Value type is the data structure representing the data in a single keyed value.
string | number | booleanA Value is used when setting a value with the setValue method, and when getting it back out again with the getValue method. A Value is a JavaScript string, number, or boolean.
Example
import type {Value} from 'tinybase';
export const value: Value = 'dog';
Since
v3.0.0
ValueOrUndefined
The ValueOrUndefined type is a data structure representing the data in a single value, or the value undefined.
Value | undefinedThis is used when describing a Value that is present or that is not present, such as when it has been deleted, or when describing a previous state where the Value has since been added.
Since
v3.0.0
Content
The Content type describes both the Tables and Values in a Store.
[Tables, Values]It is an array of two objects, representing tabular and keyed value content.
Example
The following is a valid Content array:
[
{
"pets": {
"fido": {
"sold": false,
"price": 4,
},
"felix": {
"sold": true,
"price": 5,
},
},
},
{
open: true,
employees: 3,
},
]
Since
v5.0.0
Transaction type aliases
ChangedTableIds
The ChangedTableIds type describes the Table Ids that were added or removed during a transaction.
{[tableId: Id]: IdAddedOrRemoved}It is available to the DoRollback function and to a TransactionListener function via the TransactionLog object.
It is a simple object that has Table Id as key, and an IdAddedOrRemoved number indicating whether the Table Id was added (1) or removed (-1).
Note that there will be no entry if the content of the Table itself changed. For that you should consult the ChangedRowIds, ChangedCellIds, or ChangedCells types.
Since
v4.0.0
ChangedRowIds
The ChangedRowIds type describes the Row Ids that were added or removed during a transaction.
{[tableId: Id]: {[rowId: Id]: IdAddedOrRemoved}}It is available to the DoRollback function and to a TransactionListener function via the TransactionLog object.
It is a nested object that has Table Id as a top-level key, and then Row Id as an inner key. The values of the inner objects are IdAddedOrRemoved numbers indicating whether the Row Id was added (1) or removed (-1) to the given Table.
Note that there will be no entry if the content of the Row itself changed. For that you should consult the ChangedCellIds or ChangedCells types.
Since
v4.0.0
ChangedCellIds
The ChangedCellIds type describes the Cell Ids that were added or removed during a transaction.
{[tableId: Id]: {[rowId: Id]: {[cellId: Id]: IdAddedOrRemoved}}}It is available to the DoRollback function and to a TransactionListener function via the TransactionLog object.
It is a nested object that has Table Id as a top-level key, and Row Id, and then CellId as inner keys. The values of the inner objects are IdAddedOrRemoved numbers indicating whether the Cell Id was added (1) or removed (-1) to the given Row.
Note that there will be no entry if the content of the Cell itself changed. For that you should consult the ChangedCells type.
Since
v4.0.0
ChangedCell
The ChangedCell type describes a Cell that has been changed during a transaction, primarily used so that you can indicate whether the transaction should be rolled back.
[oldCell: CellOrUndefined, newCell: CellOrUndefined]It provides both the old and new Cell values in a two-part array. These are describing the state of the changed Cell in the Store at the start of the transaction, and by the end of the transaction.
Hence, an undefined value for the first item in the array means that the Cell was added during the transaction. An undefined value for the second item in the array means that the Cell was removed during the transaction. An array with two different Cell values indicates that it was changed. The two-part array will never contain two items of the same value (including two undefined values), even if, during the transaction, a Cell was changed to a different value and then changed back.
Since
v1.2.0
ChangedCells
The ChangedCells type describes the Cell values that have been changed during a transaction, primarily used so that you can indicate whether the transaction should be rolled back.
{[tableId: Id]: {[rowId: Id]: {[cellId: Id]: ChangedCell}}}A ChangedCells object is provided to the doRollback callback when using the transaction method and the finishTransaction method. See those methods for specific examples.
This type is a nested structure of Table, Row, and Cell objects, much like the Tables object, but one which provides both the old and new Cell values in a two-part array. These are describing the state of each changed Cell in Store at the start of the transaction, and by the end of the transaction.
Hence, an undefined value for the first item in the array means that the Cell was added during the transaction. An undefined value for the second item in the array means that the Cell was removed during the transaction. An array with two different Cell values indicates that it was changed. The two-part array will never contain two items of the same value (including two undefined values), even if, during the transaction, a Cell was changed to a different value and then changed back.
Since
v1.2.0
InvalidCells
The InvalidCells type describes the invalid Cell values that have been attempted during a transaction, primarily used so that you can indicate whether the transaction should be rolled back.
{[tableId: Id]: {[rowId: Id]: {[cellId: Id]: any[]}}}An InvalidCells object is provided to the doRollback callback when using the transaction method and the finishTransaction method. See those methods for specific examples.
This type is a nested structure of Table, Row, and Cell objects, much like the Tables object, but one for which Cell values are listed in array (much like the InvalidCellListener type) so that multiple failed attempts to change a Cell during the transaction are described.
Since
v1.2.0
ChangedValues
The ChangedValues type describes the Values that have been changed during a transaction, primarily used so that you can indicate whether the transaction should be rolled back.
{[valueId: Id]: ChangedValue}A ChangedValues object is provided to the doRollback callback when using the transaction method and the finishTransaction method. See those methods for specific examples.
This type is an object containing the old and new Values in two-part arrays. These are describing the state of each changed Value in Store at the start of the transaction, and by the end of the transaction.
Hence, an undefined value for the first item in the array means that the Value was added during the transaction. An undefined value for the second item in the array means that the Value was removed during the transaction. An array with two different Values indicates that it was changed. The two-part array will never contain two items of the same value (including two undefined values), even if, during the transaction, a Value was changed to a different value and then changed back.
Since
v3.0.0
InvalidValues
The InvalidValues type describes the invalid Values that have been attempted during a transaction, primarily used so that you can indicate whether the transaction should be rolled back.
{[valueId: Id]: any[]}An InvalidValues object is provided to the doRollback callback when using the transaction method and the finishTransaction method. See those methods for specific examples.
This type is an object containing each invalid Value's attempt listed in array (much like the InvalidValueListener type) so that multiple failed attempts to change a Value during the transaction are described.
Since
v3.0.0
ChangedValue
The ChangedValue type describes a Value that has been changed during a transaction, primarily used so that you can indicate whether the transaction should be rolled back.
[oldValue: ValueOrUndefined, newValue: ValueOrUndefined]It provides both the old and new Values in a two-part array. These describe the state of the changed Value in the Store at the start of the transaction, and by the end of the transaction.
Hence, an undefined value for the first item in the array means that the Value was added during the transaction. An undefined value for the second item in the array means that the Value was removed during the transaction. An array with two different Values indicates that it was changed. The two-part array will never contain two items of the same value (including two undefined values), even if, during the transaction, a Value was changed to a different value and then changed back.
Since
v3.0.0
ChangedValueIds
The ChangedValueIds type describes the Value Ids that were added or removed during a transaction.
{[valueId: Id]: IdAddedOrRemoved}It is available to the DoRollback function and to a TransactionListener function via the TransactionLog object.
It is a simple object that has Value Id as key, and an IdAddedOrRemoved number indicating whether the Value Id was added (1) or removed (-1).
Note that there will be no entry if the content of the Value itself changed. For that you should consult the ChangedValues type.
Since
v4.0.0
Changes
The Changes type describes the net meaningful changes that were made to a Store during a transaction.
[changedTables: {[tableId: Id]: {[rowId: Id]: {[cellId: Id]: CellOrUndefined} | undefined} | undefined}, changedValues: {[valueId: Id]: ValueOrUndefined}, isChanges: 1]This contains mostly equivalent information to a TransactionLog, but in a form that can be more efficiently parsed and serialized (for example in the case of synchronization between systems).
It is an array of two objects, representing tabular and keyed value changes. If the first item is an empty object, it means no tabular changes were made. If the second item is an empty object, it means no keyed value changes were made.
If not empty, the first object has an entry for each Table in a Store that has had a change within it. If the entry is null, it means that whole Table was deleted. Otherwise, the entry will be an object with an entry for each Row in that Table that had a change within it. In turn, if that entry is null, it means the Row was deleted. Otherwise, the entry will be an object with an entry for each Cell in that Row that had a change within it. If the entry is null, the Cell was deleted, otherwise it will contain the new value the Cell was changed to during the transaction.
If not empty, the second object has an entry for each Value in a Store that has had a change. If the entry is null, the Value was deleted, otherwise it will contain the new Value it was changed to during the transaction.
A third, required, item in the array is the digit 1, so that instances of Content and Changes types can be disambiguated.
Example
The following is a valid Changes array that conveys the following:
[
{ // changes to tabular data in the Store
"pets": { // this Table was changed
"fido": null, // this Row was deleted
"felix": { // this Row was changed
"sold": true, // this Cell was changed
"price": null, // this Cell was deleted
},
},
"pendingSales": null, // this Table was deleted
},
{}, // no changes to keyed value data in the Store
1, // indicates that this is a Changes array
]
Since
v4.0.0
IdAddedOrRemoved
The IdAddedOrRemoved type describes a change made to an Id in either the tabular or keyed-value part of the Store (or in other TinyBase modules).
1 | -1This type is used in other types like ChangedTableIds, ChangedRowIds, ChangedCellIds, and ChangedValueIds, as well as in other events or listeners where a list of Ids has changed.
It is a simple number: a 1 indicates that a given Id was added to a list of Ids, and a -1 indicates that it was removed.
Since
v4.0.0
TransactionLog
The TransactionLog type describes the changes that were made to a Store during a transaction in detail.
[cellsTouched: boolean, valuesTouched: boolean, changedCells: ChangedCells, invalidCells: InvalidCells, changedValues: ChangedValues, invalidValues: InvalidValues, changedTableIds: ChangedTableIds, changedRowIds: ChangedRowIds, changedCellIds: ChangedCellIds, changedValueIds: ChangedValueIds]This contains equivalent information to a Changes object, but also information about what the previous state of the Store was. The changedCells and changedValues entries contain information about all changes to those parts of the Store, with their before and after values, for example.
cellsTouched and valuesTouched indicate whether Cell or Value data has been touched during the transaction. The two flags are intended as a hint about whether non-mutating listeners might be being called at the end of the transaction.
Here, 'touched' means that Cell or Value data has either been changed, or changed and then changed back to its original value during the transaction. The exception is a transaction that has been rolled back, for which the value of cellsTouched and valuesTouched in the listener will be false because all changes have been reverted.
In v5.0, this type changed from an object to an array, but still contains the same values.
See the documentation for the types of the inner objects for other details.
Since
v4.0.0
Development type aliases
StoreListenerStats
The StoreListenerStats type describes the number of listeners registered with the Store, and can be used for debugging purposes.
{
hasTables: number;
tables: number;
tableIds: number;
hasTable: number;
table: number;
tableCellIds: number;
hasTableCell: number;
rowCount: number;
rowIds: number;
sortedRowIds: number;
hasRow: number;
row: number;
cellIds: number;
hasCell: number;
cell: number;
invalidCell: number;
hasValues: number;
values: number;
valueIds: number;
hasValue: number;
value: number;
invalidValue: number;
transaction: number;
}| Type | Description | |
|---|---|---|
hasTables | number | The number of |
tables | number | The number of |
tableIds | number | The number of |
hasTable | number | The number of |
table | number | The number of |
tableCellIds | number | The number of |
hasTableCell | number | The number of |
rowCount | number | The number of |
rowIds | number | The number of |
sortedRowIds | number | The number of |
hasRow | number | The number of |
row | number | The number of |
cellIds | number | The number of |
hasCell | number | The number of |
cell | number | The number of |
invalidCell | number | The number of |
hasValues | number | The number of |
values | number | The number of |
valueIds | number | The number of |
hasValue | number | The number of |
value | number | The number of |
invalidValue | number | The number of |
transaction | number | The number of |
The StoreListenerStats object contains a breakdown of the different types of listener. Totals include both mutator and non-mutator listeners. A StoreListenerStats object is returned from the getListenerStats method.
Since
v1.0.0
mergeable-store
The mergeable-store module contains the types, interfaces, and functions to work with MergeableStore objects, which provide merge and synchronization functionality.
The main entry point to this module is the createMergeableStore function, which returns a new MergeableStore, a subtype of Store that can be merged with another with deterministic results.
Please be aware that a lot of the types and methods exposed by this module are used internally within TinyBase itself (in particular the Synchronizer framework). They're documented here, but mostly for interest, and it is generally assumed that they won't be called directly by applications.
As an application developer, it's more likely that you will continue to use the main Store methods for reading, writing, and listening to data, and rely on Synchronizer instances to keep the data in step with other places.
Since
v5.0.0
Interfaces
MergeableStore
The MergeableStore type represents a Store that carries with it sufficient metadata to be able to be merged with another MergeableStore with deterministic results.
This is the key data type used when you need TinyBase data to be cleanly synchronized or merged with data elsewhere on the system, or on another system. It acts as a Conflict-Free Replicated Data Type (CRDT) which allows deterministic disambiguation of how changes to different instances should be merged.
Please be aware that a lot of the methods exposed by this interface are used internally within TinyBase itself (in particular the Synchronizer framework). They're documented here, but mostly for interest, and it is generally assumed that they won't be called directly by applications.
As an application developer, it's more likely that you will continue to use the main Store methods for reading, writing, and listening to data, and rely on Synchronizer instances to keep the data in step with other places.
One possible exceptions is the merge method, which can be used to simply merge two co-located MergeableStore instances together.
Example
This example shows very simple usage of the MergeableStore: whereby two are created, updated with different data, and then merged with one another.
import {createMergeableStore} from 'tinybase';
const localStore1 = createMergeableStore();
const localStore2 = createMergeableStore();
localStore1.setCell('pets', 'fido', 'color', 'brown');
localStore2.setCell('pets', 'felix', 'color', 'black');
localStore1.merge(localStore2);
console.log(localStore1.getContent());
// -> [{pets: {felix: {color: 'black'}, fido: {color: 'brown'}}}, {}]
console.log(localStore2.getContent());
// -> [{pets: {felix: {color: 'black'}, fido: {color: 'brown'}}}, {}]
Since
v5.0.0
Getter methods
getTables
The getTables method returns a Tables object containing the entire tabular data of the Store.
getTables(): TablesNote that this returns a copy of, rather than a reference to the underlying data, so changes made to the returned object are not made to the Store itself.
Examples
This example retrieves the tabular data in a Store.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}, species: {dog: {price: 5}}}
This example retrieves the Tables of an empty Store, returning an empty object.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getTables());
// -> {}
Since
v1.0.0
getTablesJson
The getTablesJson method returns a string serialization of all of the Tables in the Store.
getTablesJson(): stringExamples
This example serializes the contents of a Store.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getTablesJson());
// -> '{"pets":{"fido":{"species":"dog"}}}'
This example serializes the contents of an empty Store.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getTablesJson());
// -> '{}'
Since
v3.0.0
getTablesSchemaJson
The getTablesSchemaJson method returns a string serialization of the TablesSchema of the Store.
getTablesSchemaJson(): string| returns | string | A string serialization of the |
|---|
If no TablesSchema has been set on the Store (or if it has been removed with the delTablesSchema method), then it will return the serialization of an empty object, {}.
Examples
This example serializes the TablesSchema of a Store.
import {createStore} from 'tinybase';
const store = createStore().setTablesSchema({
pets: {
species: {type: 'string'},
sold: {type: 'boolean'},
},
});
console.log(store.getTablesSchemaJson());
// -> '{"pets":{"species":{"type":"string"},"sold":{"type":"boolean"}}}'
This example serializes the TablesSchema of an empty Store.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getTablesSchemaJson());
// -> '{}'
Since
v3.0.0
hasTables
The hasTables method returns a boolean indicating whether any Table objects exist in the Store.
hasTables(): boolean| returns | boolean | Whether any |
|---|
Example
This example shows simple existence checks.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.hasTables());
// -> false
store.setTables({pets: {fido: {species: 'dog'}}});
console.log(store.hasTables());
// -> true
Since
v1.0.0
hasTablesSchema
The hasTablesSchema method returns a boolean indicating whether the Store currently has a TablesSchema applied to it.
hasTablesSchema(): boolean| returns | boolean | Whether the |
|---|
Example
This example sets a TablesSchema and checks that it is present.
import {createStore} from 'tinybase';
const store = createStore().setTablesSchema({
pets: {
price: {type: 'number'},
},
});
console.log(store.hasTablesSchema());
// -> true
store.delTablesSchema();
console.log(store.hasTablesSchema());
// -> false
Since
v4.1.1
getTableIds
The getTableIds method returns the Ids of every Table in the Store.
getTableIds(): IdsNote that this returns a copy of, rather than a reference, to the list of Ids, so changes made to the list are not made to the Store itself.
Examples
This example retrieves the Table Ids in a Store.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
console.log(store.getTableIds());
// -> ['pets', 'species']
This example retrieves the Table Ids of an empty Store, returning an empty array.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getTableIds());
// -> []
Since
v1.0.0
getTable
The getTable method returns an object containing the entire data of a single Table in the Store.
getTable(tableId: string): TableNote that this returns a copy of, rather than a reference to the underlying data, so changes made to the returned object are not made to the Store itself.
Examples
This example retrieves the data in a single Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
console.log(store.getTable('pets'));
// -> {fido: {species: 'dog'}}
This example retrieves a Table that does not exist, returning an empty object.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getTable('employees'));
// -> {}
Since
v1.0.0
getTableCellIds
The getTableCellIds method returns the Ids of every Cell used across the whole Table.
getTableCellIds(tableId: string): Ids| Type | Description | |
|---|---|---|
tableId | string | |
| returns | Ids | An array of the |
Note that this returns a copy of, rather than a reference, to the list of Ids, so changes made to the list are not made to the Store itself.
Examples
This example retrieves the Cell Ids used across a whole Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', legs: 4},
cujo: {dangerous: true},
},
});
console.log(store.getTableCellIds('pets'));
// -> ['species', 'color', 'legs', 'dangerous']
This example retrieves the Cell Ids used across a Table that does not exist, returning an empty array.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getTableCellIds('species'));
// -> []
Since
v3.3.0
hasTable
The hasTable method returns a boolean indicating whether a given Table exists in the Store.
hasTable(tableId: string): boolean| Type | Description | |
|---|---|---|
tableId | string | |
| returns | boolean |
Example
This example shows two simple Table existence checks.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.hasTable('pets'));
// -> true
console.log(store.hasTable('employees'));
// -> false
Since
v1.0.0
hasTableCell
The hasTableCell method returns a boolean indicating whether a given Cell exists anywhere in a Table, not just in a specific Row.
hasTableCell(
tableId: string,
cellId: string,
): boolean| Type | Description | |
|---|---|---|
tableId | string | |
cellId | string | |
| returns | boolean |
Example
This example shows two simple Cell existence checks.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}, felix: {legs: 4}},
});
console.log(store.hasTableCell('pets', 'species'));
// -> true
console.log(store.hasTableCell('pets', 'legs'));
// -> true
console.log(store.hasTableCell('pets', 'color'));
// -> false
Since
v3.3.0
getRowIds
The getRowIds method returns the Ids of every Row in a given Table.
getRowIds(tableId: string): Ids| Type | Description | |
|---|---|---|
tableId | string | |
| returns | Ids |
Note that this returns a copy of, rather than a reference, to the list of Ids, so changes made to the list are not made to the Store itself.
Examples
This example retrieves the Row Ids in a Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
console.log(store.getRowIds('pets'));
// -> ['fido', 'felix']
This example retrieves the Row Ids of a Table that does not exist, returning an empty array.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getRowIds('employees'));
// -> []
Since
v1.0.0
getSortedRowIds
The getSortedRowIds method returns the Ids of every Row in a given Table, sorted according to the values in a specified Cell.
getSortedRowIds(
tableId: string,
cellId?: string,
descending?: boolean,
offset?: number,
limit?: number,
): Ids| Type | Description | |
|---|---|---|
tableId | string | |
cellId? | string | The |
descending? | boolean | Whether the sorting should be in descending order. |
offset? | number | The number of |
limit? | number | The maximum number of |
| returns | Ids | An array of the sorted |
The sorting of the rows is alphanumeric, and you can indicate whether it should be in descending order. The offset and limit parameters are used to paginate results, but default to 0 and undefined to return all available Row Ids if not specified.
Note that every call to this method will perform the sorting afresh - there is no caching of the results - and so you are advised to memoize the results yourself, especially when the Table is large. For a performant approach to tracking the sorted Row Ids when they change, use the addSortedRowIdsListener method.
If the Table does not exist, an empty array is returned.
Examples
This example retrieves sorted Row Ids in a Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
console.log(store.getSortedRowIds('pets', 'species'));
// -> ['felix', 'fido']
This example retrieves sorted Row Ids in a Table in reverse order.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'wolf'},
},
});
console.log(store.getSortedRowIds('pets', 'species', true));
// -> ['cujo', 'fido', 'felix']
This example retrieves two pages of Row Ids in a Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {price: 6},
felix: {price: 5},
mickey: {price: 2},
tom: {price: 4},
carnaby: {price: 3},
lowly: {price: 1},
},
});
console.log(store.getSortedRowIds('pets', 'price', false, 0, 2));
// -> ['lowly', 'mickey']
console.log(store.getSortedRowIds('pets', 'price', false, 2, 2));
// -> ['carnaby', 'tom']
This example retrieves Row Ids sorted by their own value, since the cellId parameter is undefined.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'wolf'},
},
});
console.log(store.getSortedRowIds('pets'));
// -> ['cujo', 'felix', 'fido']
This example retrieves the sorted Row Ids of a Table that does not exist, returning an empty array.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getSortedRowIds('employees'));
// -> []
Since
v2.0.0
When called with one object argument, the getSortedRowIds method destructures it to make it easier to skip optional parameters.
getSortedRowIds(args: SortedRowIdsArgs): Ids| Type | Description | |
|---|---|---|
args | SortedRowIdsArgs | A |
| returns | Ids | An array of the sorted |
Example
This example retrieves the first sorted Row Id in a Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'wolf'},
},
});
console.log(store.getSortedRowIds({tableId: 'pets', limit: 1}));
// -> ['cujo']
Since
v6.1.0
getRow
The getRow method returns an object containing the entire data of a single Row in a given Table.
getRow(
tableId: string,
rowId: string,
): RowNote that this returns a copy of, rather than a reference to the underlying data, so changes made to the returned object are not made to the Store itself.
Examples
This example retrieves the data in a single Row.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
console.log(store.getRow('pets', 'fido'));
// -> {species: 'dog'}
This example retrieves a Row that does not exist, returning an empty object.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getRow('pets', 'felix'));
// -> {}
Since
v1.0.0
getRowCount
The getRowCount method returns the count of the Row objects in a given Table.
getRowCount(tableId: string): number| Type | Description | |
|---|---|---|
tableId | string | |
| returns | number |
While this provides the same result as the length of Ids array returned from the getRowIds method, it is somewhat faster, and useful for efficient pagination.
Examples
This example retrieves the number of Row objects in the Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
console.log(store.getRowCount('pets'));
// -> 2
This example retrieves the Row Ids of a Table that does not exist, returning zero.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getRowCount('employees'));
// -> 0
Since
v4.1.0
hasRow
The hasRow method returns a boolean indicating whether a given Row exists in the Store.
hasRow(
tableId: string,
rowId: string,
): boolean| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
| returns | boolean |
Example
This example shows two simple Row existence checks.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.hasRow('pets', 'fido'));
// -> true
console.log(store.hasRow('pets', 'felix'));
// -> false
Since
v1.0.0
getCellIds
The getCellIds method returns the Ids of every Cell in a given Row in a given Table.
getCellIds(
tableId: string,
rowId: string,
): Ids| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
| returns | Ids |
Note that this returns a copy of, rather than a reference, to the list of Ids, so changes made to the list are not made to the Store itself.
Examples
This example retrieves the Cell Ids in a Row.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog', color: 'brown'},
},
});
console.log(store.getCellIds('pets', 'fido'));
// -> ['species', 'color']
This example retrieves the Cell Ids of a Row that does not exist, returning an empty array.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getCellIds('pets', 'felix'));
// -> []
Since
v1.0.0
getCell
The getCell method returns the value of a single Cell in a given Row, in a given Table.
getCell(
tableId: string,
rowId: string,
cellId: string,
): CellOrUndefined| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
| returns | CellOrUndefined | The value of the |
Examples
This example retrieves a single Cell.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
console.log(store.getCell('pets', 'fido', 'species'));
// -> 'dog'
This example retrieves a Cell that does not exist, returning undefined.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getCell('pets', 'fido', 'color'));
// -> undefined
Since
v1.0.0
hasCell
The hasCell method returns a boolean indicating whether a given Cell exists in a given Row in a given Table.
hasCell(
tableId: string,
rowId: string,
cellId: string,
): boolean| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
| returns | boolean | Whether a |
Example
This example shows two simple Cell existence checks.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.hasCell('pets', 'fido', 'species'));
// -> true
console.log(store.hasCell('pets', 'fido', 'color'));
// -> false
Since
v1.0.0
getValues
The getValues method returns an object containing the entire set of keyed Values in the Store.
getValues(): ValuesNote that this returns a copy of, rather than a reference to the underlying data, so changes made to the returned object are not made to the Store itself.
Examples
This example retrieves the set of keyed Values in the Store.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
console.log(store.getValues());
// -> {open: true, employees: 3}
This example retrieves Values from a Store that has none, returning an empty object.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getValues());
// -> {}
Since
v3.0.0
getValuesJson
The getValuesJson method returns a string serialization of all of the keyed Values in the Store.
getValuesJson(): stringExamples
This example serializes the keyed value contents of a Store.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
console.log(store.getValuesJson());
// -> '{"open":true}'
This example serializes the contents of an empty Store.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getValuesJson());
// -> '{}'
Since
v3.0.0
getValuesSchemaJson
The getValuesSchemaJson method returns a string serialization of the ValuesSchema of the Store.
getValuesSchemaJson(): string| returns | string | A string serialization of the |
|---|
If no ValuesSchema has been set on the Store (or if it has been removed with the delValuesSchema method), then it will return the serialization of an empty object, {}.
Examples
This example serializes the ValuesSchema of a Store.
import {createStore} from 'tinybase';
const store = createStore().setValuesSchema({
open: {type: 'boolean', default: false},
});
console.log(store.getValuesSchemaJson());
// -> '{"open":{"type":"boolean","default":false}}'
This example serializes the ValuesSchema of an empty Store.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getValuesSchemaJson());
// -> '{}'
Since
v3.0.0
hasValues
The hasValues method returns a boolean indicating whether any Values exist in the Store.
hasValues(): boolean| returns | boolean | Whether any |
|---|
Example
This example shows simple existence checks.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.hasValues());
// -> false
store.setValues({open: true});
console.log(store.hasValues());
// -> true
Since
v3.0.0
hasValuesSchema
The hasValuesSchema method returns a boolean indicating whether the Store currently has a ValuesSchema applied to it.
hasValuesSchema(): boolean| returns | boolean | Whether the |
|---|
Example
This example sets a ValuesSchema and checks that it is present.
import {createStore} from 'tinybase';
const store = createStore().setValuesSchema({open: {type: 'boolean'}});
console.log(store.hasValuesSchema());
// -> true
store.delValuesSchema();
console.log(store.hasValuesSchema());
// -> false
Since
v4.1.1
getValue
The getValue method returns a single keyed Value in the Store.
getValue(valueId: string): ValueOrUndefined| Type | Description | |
|---|---|---|
valueId | string | |
| returns | ValueOrUndefined | The |
Examples
This example retrieves a single Value.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
console.log(store.getValue('employees'));
// -> 3
This example retrieves a Value that does not exist, returning undefined.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
console.log(store.getValue('website'));
// -> undefined
Since
v3.0.0
getValueIds
The getValueIds method returns the Ids of every Value in a Store.
getValueIds(): IdsNote that this returns a copy of, rather than a reference, to the list of Ids, so changes made to the list are not made to the Store itself.
Examples
This example retrieves the Value Ids in a Store.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
console.log(store.getValueIds());
// -> ['open', 'employees']
This example retrieves the Value Ids of a Store that has had none set, returning an empty array.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getValueIds());
// -> []
Since
v3.0.0
hasValue
The hasValue method returns a boolean indicating whether a given Value exists in the Store.
hasValue(valueId: string): boolean| Type | Description | |
|---|---|---|
valueId | string | |
| returns | boolean |
Example
This example shows two simple Value existence checks.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
console.log(store.hasValue('open'));
// -> true
console.log(store.hasValue('employees'));
// -> false
Since
v3.0.0
getContent
The getContent method returns a Tables object and a Values object in an array, representing the entire content of the Store.
getContent(): ContentNote that this returns a copy of, rather than a reference to the underlying data, so changes made to the returned objects are not made to the Store itself.
Examples
This example retrieves the content of a Store.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({pets: {fido: {species: 'dog'}}})
.setValues({open: true, employees: 3});
console.log(store.getContent());
// -> [{pets: {fido: {species: 'dog'}}}, {open: true, employees: 3}]
This example retrieves the Tables and Values of an empty Store, returning empty objects.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getContent());
// -> [{}, {}]
Since
v4.0.0
getJson
The getJson method returns a string serialization of all the Store content: both the Tables and the keyed Values.
getJson(): stringFrom v3.0 onwards, the serialization is of an array with two entries. The first is the Tables object, the second the Values. In previous versions (before the existence of the Values data structure), it was a sole object of Tables.
Examples
This example serializes the tabular and keyed value contents of a Store.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({pets: {fido: {species: 'dog'}}})
.setValues({open: true});
console.log(store.getJson());
// -> '[{"pets":{"fido":{"species":"dog"}}},{"open":true}]'
This example serializes the contents of an empty Store.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getJson());
// -> '[{},{}]'
Since
v1.0.0
getMergeableContent
The getMergeableContent method returns the full content of a MergeableStore, together with the metadata required to make it mergeable with another.
getMergeableContent(): MergeableContent| returns | MergeableContent | A |
|---|
The method is generally intended to be used internally within TinyBase itself and the return type is assumed to be opaque to applications that use it.
Example
This example creates a MergeableStore, sets some data, and then accesses the content and metadata required to make it mergeable.
import {createMergeableStore} from 'tinybase';
const store = createMergeableStore('store1');
store.setCell('pets', 'fido', 'color', 'brown');
console.log(store.getMergeableContent());
// ->
[
[
{
pets: [
{
fido: [
{color: ['brown', 'Nn1JUF-----FnHIC', 923684530]},
'',
851131566,
],
},
'',
518810247,
],
},
'',
784336119,
],
[{}, '', 0],
];
Since
v5.0.0
getSchemaJson
The getSchemaJson method returns a string serialization of both the TablesSchema and ValuesSchema of the Store.
getSchemaJson(): string| returns | string | A string serialization of the |
|---|
From v3.0 onwards, the serialization is of an array with two entries. The first is the TablesSchema object, the second the ValuesSchema. In previous versions (before the existence of the ValuesSchema data structure), it was a sole object of TablesSchema.
Examples
This example serializes the TablesSchema and ValuesSchema of a Store.
import {createStore} from 'tinybase';
const store = createStore()
.setTablesSchema({
pets: {
price: {type: 'number'},
},
})
.setValuesSchema({
open: {type: 'boolean'},
});
console.log(store.getSchemaJson());
// -> '[{"pets":{"price":{"type":"number"}}},{"open":{"type":"boolean"}}]'
This example serializes the TablesSchema and ValuesSchema of an empty Store.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getSchemaJson());
// -> '[{},{}]'
Since
v1.0.0
isMergeable
The isMergeable method lets you know if the Store is mergeable.
isMergeable(): boolean| returns | boolean | Whether the |
|---|
This will always return false for a Store, and true for a MergeableStore.
Since
v5.0.0
Setter methods
setTables
The setTables method takes an object and sets the entire tabular data of the Store.
setTables(tables: Tables): thisThis method will cause listeners to be called for any Table, Row, Cell, or Id changes resulting from it.
Any part of the provided object that is invalid (either according to the Tables type, or because it does not match a TablesSchema associated with the Store), will be ignored silently.
Assuming that at least some of the provided Tables object is valid, any data that was already present in the Store will be completely overwritten. If the object is completely invalid, no change will be made to the Store.
The method returns a reference to the Store so that subsequent operations can be chained in a fluent style.
Examples
This example sets the tabular data of a Store.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}, species: {dog: {price: 5}}}
This example attempts to set the tabular data of an existing Store with partly invalid, and then completely invalid, Tables objects.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.setTables({pets: {felix: {species: 'cat', bug: []}}});
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
store.setTables({meaning: 42});
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
Since
v1.0.0
setTablesJson
The setTablesJson method takes a string serialization of all of the Tables in the Store and attempts to update them to that.
setTablesJson(tablesJson: string): this| Type | Description | |
|---|---|---|
tablesJson | string | |
| returns | this | A reference to the |
If the JSON cannot be parsed, this will fail silently. If it can be parsed, it will then be subject to the same validation rules as the setTables method (according to the Tables type, and matching any TablesSchema associated with the Store).
Examples
This example sets the tabular contents of a Store from a serialization.
import {createStore} from 'tinybase';
const store = createStore();
store.setTablesJson('{"pets": {"fido": {"species": "dog"}}}');
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
This example attempts to set the tabular contents of a Store from an invalid serialization.
import {createStore} from 'tinybase';
const store = createStore();
store.setTablesJson('{"pets": {"fido": {');
console.log(store.getTables());
// -> {}
Since
v3.0.0
setTablesSchema
The setTablesSchema method lets you specify the TablesSchema of the tabular part of the Store.
setTablesSchema(tablesSchema: TablesSchema): this| Type | Description | |
|---|---|---|
tablesSchema | TablesSchema | The |
| returns | this | A reference to the |
Note that this may result in a change to data in the Store, as defaults are applied or as invalid Table, Row, or Cell objects are removed. These changes will fire any listeners to that data, as expected.
When no longer needed, you can also completely remove an existing TablesSchema with the delTablesSchema method.
Example
This example sets the TablesSchema of a Store after it has been created.
import {createStore} from 'tinybase';
const store = createStore().setTablesSchema({
pets: {
species: {type: 'string'},
sold: {type: 'boolean', default: false},
},
});
store.addRow('pets', {species: 'dog', color: 'brown', sold: 'maybe'});
console.log(store.getTables());
// -> {pets: {0: {species: 'dog', sold: false}}}
Since
v3.0.0
setTable
The setTable method takes an object and sets the entire data of a single Table in the Store.
setTable(
tableId: string,
table: Table,
): this| Type | Description | |
|---|---|---|
tableId | string | |
table | Table | The data of a single |
| returns | this | A reference to the |
This method will cause listeners to be called for any Table, Row, Cell, or Id changes resulting from it.
Any part of the provided object that is invalid (either according to the Table type, or because it does not match a TablesSchema associated with the Store), will be ignored silently.
Assuming that at least some of the provided Table object is valid, any data that was already present in the Store for that Table will be completely overwritten. If the object is completely invalid, no change will be made to the Store.
The method returns a reference to the Store so that subsequent operations can be chained in a fluent style.
Examples
This example sets the data of a single Table.
import {createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
This example attempts to set the data of an existing Store with partly invalid, and then completely invalid, Table objects.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.setTable('pets', {felix: {species: 'cat', bug: []}});
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
store.setTable('pets', {meaning: 42});
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
Since
v1.0.0
setRow
The setRow method takes an object and sets the entire data of a single Row in the Store.
setRow(
tableId: string,
rowId: string,
row: Row,
): this| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
row | Row | The data of a single |
| returns | this | A reference to the |
This method will cause listeners to be called for any Table, Row, Cell, or Id changes resulting from it.
Any part of the provided object that is invalid (either according to the Row type, or because it does not match a TablesSchema associated with the Store), will be ignored silently.
Assuming that at least some of the provided Row object is valid, any data that was already present in the Store for that Row will be completely overwritten. If the object is completely invalid, no change will be made to the Store.
The method returns a reference to the Store so that subsequent operations can be chained in a fluent style.
Examples
This example sets the data of a single Row.
import {createStore} from 'tinybase';
const store = createStore().setRow('pets', 'fido', {species: 'dog'});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
This example attempts to set the data of an existing Store with partly invalid, and then completely invalid, Row objects.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.setRow('pets', 'fido', {color: 'brown', bug: []});
console.log(store.getTables());
// -> {pets: {fido: {color: 'brown'}}}
store.setRow('pets', 'fido', 42);
console.log(store.getTables());
// -> {pets: {fido: {color: 'brown'}}}
Since
v1.0.0
addRow
The addRow method takes an object and creates a new Row in the Store, returning the unique Id assigned to it.
addRow(
tableId: string,
row: Row,
reuseRowIds?: boolean,
): undefined | string| Type | Description | |
|---|---|---|
tableId | string | |
row | Row | The data of a single |
reuseRowIds? | boolean | Whether |
| returns | undefined | string | A reference to the |
This method will cause listeners to be called for any Table, Row, Cell, or Id changes resulting from it.
Any part of the provided object that is invalid (either according to the Row type, or because it does not match a TablesSchema associated with the Store), will be ignored silently.
Assuming that at least some of the provided Row object is valid, a new Row will be created. If the object is completely invalid, no change will be made to the Store and the method will return undefined.
You should not guarantee the form of the unique Id that is generated when a Row is added to the Table. However it is likely to be a string representation of an increasing integer.
The reuseRowIds parameter defaults to true, which means that if you delete a Row and then add another, the Id will be re-used - unless you delete the entire Table, in which case all Row Ids will reset. Otherwise, if you specify reuseRowIds to be false, then the Id will be a monotonically increasing string representation of an increasing integer, regardless of any you may have previously deleted.
Examples
This example adds a single Row.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.addRow('pets', {species: 'dog'}));
// -> '0'
console.log(store.getTables());
// -> {pets: {'0': {species: 'dog'}}}
This example attempts to add Rows to an existing Store with partly invalid, and then completely invalid, Row objects.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {'0': {species: 'dog'}}});
console.log(store.addRow('pets', {species: 'cat', bug: []}));
// -> '1'
console.log(store.getTables());
// -> {pets: {'0': {species: 'dog'}, '1': {species: 'cat'}}}
console.log(store.addRow('pets', 42));
// -> undefined
console.log(store.getTables());
// -> {pets: {'0': {species: 'dog'}, '1': {species: 'cat'}}}
Since
v1.0.0
setPartialRow
The setPartialRow method takes an object and sets partial data of a single Row in the Store, leaving other Cell values unaffected.
setPartialRow(
tableId: string,
rowId: string,
partialRow: Row,
): this| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
partialRow | Row | The partial data of a single |
| returns | this | A reference to the |
This method will cause listeners to be called for any Table, Row, Cell, or Id changes resulting from it.
Any part of the provided object that is invalid (either according to the Row type, or because, when combined with the current Row data, it does not match a TablesSchema associated with the Store), will be ignored silently.
Assuming that at least some of the provided Row object is valid, it will be merged with the data that was already present in the Store. If the object is completely invalid, no change will be made to the Store.
The method returns a reference to the Store so that subsequent operations can be chained in a fluent style.
Examples
This example sets some of the data of a single Row.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
store.setPartialRow('pets', 'fido', {color: 'walnut', visits: 1});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'walnut', visits: 1}}}
This example attempts to set some of the data of an existing Store with partly invalid, and then completely invalid, Row objects.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.setPartialRow('pets', 'fido', {color: 'brown', bug: []});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'brown'}}}
store.setPartialRow('pets', 'fido', 42);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'brown'}}}
Since
v1.0.0
setCell
The setCell method sets the value of a single Cell in the Store.
setCell(
tableId: string,
rowId: string,
cellId: string,
cell: Cell | MapCell,
): this| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
cell | Cell | MapCell | The value of the |
| returns | this | A reference to the |
This method will cause listeners to be called for any Table, Row, Cell, or Id changes resulting from it.
If the Cell value is invalid (either because of its type, or because it does not match a TablesSchema associated with the Store), will be ignored silently.
As well as string, number, or boolean Cell types, this method can also take a MapCell function that takes the current Cell value as a parameter and maps it. This is useful if you want to efficiently increment a value without fetching it first, for example.
The method returns a reference to the Store so that subsequent operations can be chained in a fluent style.
Examples
This example sets the value of a single Cell.
import {createStore} from 'tinybase';
const store = createStore().setCell('pets', 'fido', 'species', 'dog');
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
This example sets the data of a single Cell by mapping the existing value.
import {createStore} from 'tinybase';
const increment = (cell) => cell + 1;
const store = createStore().setTables({pets: {fido: {visits: 1}}});
store.setCell('pets', 'fido', 'visits', increment);
console.log(store.getCell('pets', 'fido', 'visits'));
// -> 2
This example attempts to set the data of an existing Store with an invalid Cell value.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.setCell('pets', 'fido', 'bug', []);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
Since
v1.0.0
setPartialValues
The setPartialValues method takes an object and sets its Values in the Store, but leaving existing Values unaffected.
setPartialValues(partialValues: Values): thisThis method will cause listeners to be called for any Values or Id changes resulting from it.
Any part of the provided object that is invalid (either according to the Values type, or because, when combined with the current Values data, it does not match a ValuesSchema associated with the Store), will be ignored silently.
Assuming that at least some of the provided Values object is valid, it will be merged with the data that was already present in the Store. If the object is completely invalid, no change will be made to the Store.
The method returns a reference to the Store so that subsequent operations can be chained in a fluent style.
Examples
This example sets some of the keyed value data in a Store.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
store.setPartialValues({employees: 3});
console.log(store.getValues());
// -> {open: true, employees: 3}
This example attempts to set some of the data of an existing Store with partly invalid, and then completely invalid, Values objects.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
store.setPartialValues({employees: 3, bug: []});
console.log(store.getValues());
// -> {open: true, employees: 3}
store.setPartialValues(42);
console.log(store.getValues());
// -> {open: true, employees: 3}
Since
v3.0.0
setValues
The setValues method takes an object and sets all the Values in the Store.
setValues(values: Values): thisThis method will cause listeners to be called for any Value or Id changes resulting from it.
Any part of the provided object that is invalid (either according to the Values type, or because it does not match a ValuesSchema associated with the Store), will be ignored silently.
Assuming that at least some of the provided Values object is valid, any data that was already present in the Store for that Values will be completely overwritten. If the object is completely invalid, no change will be made to the Store.
The method returns a reference to the Store so that subsequent operations can be chained in a fluent style.
Examples
This example sets the Values of a Store.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
console.log(store.getValues());
// -> {open: true, employees: 3}
This example attempts to set the data of an existing Store with partly invalid, and then completely invalid, Values objects.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
store.setValues({employees: 3, bug: []});
console.log(store.getValues());
// -> {employees: 3}
store.setValues(42);
console.log(store.getValues());
// -> {employees: 3}
Since
v3.0.0
setValuesJson
The setValuesJson method takes a string serialization of all of the Values in the Store and attempts to update them to those values.
setValuesJson(valuesJson: string): this| Type | Description | |
|---|---|---|
valuesJson | string | |
| returns | this | A reference to the |
If the JSON cannot be parsed, this will fail silently. If it can be parsed, it will then be subject to the same validation rules as the setValues method (according to the Values type, and matching any ValuesSchema associated with the Store).
Examples
This example sets the keyed value contents of a Store from a serialization.
import {createStore} from 'tinybase';
const store = createStore();
store.setValuesJson('{"open": true}');
console.log(store.getValues());
// -> {open: true}
This example attempts to set the keyed value contents of a Store from an invalid serialization.
import {createStore} from 'tinybase';
const store = createStore();
store.setValuesJson('{"open": false');
console.log(store.getValues());
// -> {}
Since
v3.0.0
setValuesSchema
The setValuesSchema method lets you specify the ValuesSchema of the keyed Values part of the Store.
setValuesSchema(valuesSchema: ValuesSchema): this| Type | Description | |
|---|---|---|
valuesSchema | ValuesSchema | The |
| returns | this | A reference to the |
Note that this may result in a change to data in the Store, as defaults are applied or as invalid Values are removed. These changes will fire any listeners to that data, as expected.
When no longer needed, you can also completely remove an existing ValuesSchema with the delValuesSchema method.
Example
This example sets the ValuesSchema of a Store after it has been created.
import {createStore} from 'tinybase';
const store = createStore().setValuesSchema({
open: {type: 'boolean', default: false},
});
store.setValue('open', 'maybe');
console.log(store.getValues());
// -> {open: false}
Since
v3.0.0
setValue
The setValue method sets a single keyed Value in the Store.
setValue(
valueId: string,
value: Value | MapValue,
): thisThis method will cause listeners to be called for any Value, or Id changes resulting from it.
If the Value is invalid (either because of its type, or because it does not match a ValuesSchema associated with the Store), will be ignored silently.
As well as string, number, or boolean Value types, this method can also take a MapValue function that takes the current Value as a parameter and maps it. This is useful if you want to efficiently increment a value without fetching it first, for example.
The method returns a reference to the Store so that subsequent operations can be chained in a fluent style.
Examples
This example sets a single Value.
import {createStore} from 'tinybase';
const store = createStore().setValue('open', true);
console.log(store.getValues());
// -> {open: true}
This example sets the data of a single Value by mapping the existing Value.
import {createStore} from 'tinybase';
const increment = (value) => value + 1;
const store = createStore().setValues({employees: 3});
store.setValue('employees', increment);
console.log(store.getValue('employees'));
// -> 4
This example attempts to set an invalid Value.
import {createStore} from 'tinybase';
const store = createStore().setValues({employees: 3});
store.setValue('bug', []);
console.log(store.getValues());
// -> {employees: 3}
Since
v3.0.0
applyChanges
The applyChanges method applies a set of Changes to the Store.
applyChanges(changes: Changes): thisThis method will take a Changes object (which is available at the end of a transaction) and apply it to a Store. The most likely need to do this is to take the changes made during the transaction of one Store, and apply it to the content of another Store - such as when persisting and synchronizing data.
Any part of the provided Changes object are invalid (either because of its type, or because it does not match the schemas associated with the Store) will be ignored silently.
The method returns a reference to the Store so that subsequent operations can be chained in a fluent style.
Prior to v5.0, this method was named setTransactionChanges.
Example
This example applies a Changes object that sets a Cell and removes a Value.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({pets: {fido: {species: 'dog', color: 'brown'}}})
.setValues({open: true});
store.applyChanges([{pets: {fido: {color: 'black'}}}, {open: null}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'black'}}}
console.log(store.getValues());
// -> {}
Since
v5.0.0
applyMergeableChanges
The applyMergeableChanges method applies a set of mergeable changes or content to the MergeableStore.
applyMergeableChanges(mergeableChanges: MergeableContent | MergeableChanges<false>): MergeableStore| Type | Description | |
|---|---|---|
mergeableChanges | MergeableContent | MergeableChanges<false> | The |
| returns | MergeableStore | A reference to the |
The method is generally intended to be used internally within TinyBase itself and the return type is assumed to be opaque to applications that use it.
Example
This example applies a MergeableChanges object that sets a Cell and removes a Value.
import {createMergeableStore} from 'tinybase';
const store = createMergeableStore('store1')
.setTables({pets: {fido: {species: 'dog', color: 'brown'}}})
.setValues({open: true});
store.applyMergeableChanges([
[{pets: [{fido: [{color: ['black', 'Nn1JUF----2FnHIC']}]}]}],
[{open: [null, 'Nn1JUF----3FnHIC']}],
1,
]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'black'}}}
console.log(store.getValues());
// -> {}
Since
v5.0.0
merge
The merge method is a convenience method that applies the mergeable content from two MergeableStores to each other in order to bring them to the same state.
merge(mergeableStore: MergeableStore): MergeableStore| Type | Description | |
|---|---|---|
mergeableStore | MergeableStore | A reference to the other |
| returns | MergeableStore | A reference to this |
This method is symmetrical: applying store1 to store2 will have exactly the same effect as applying store2 to store1.
Example
This example merges two MergeableStore objects together. Note how the final part of the timestamps on each Cell give you a clue that the data comes from changes made to different MergeableStore objects.
import {createMergeableStore} from 'tinybase';
const store1 = createMergeableStore('store1');
store1.setTables({pets: {fido: {species: 'dog', color: 'brown'}}});
const store2 = createMergeableStore('store2');
store2.setTables({pets: {felix: {species: 'cat', color: 'tan'}}});
store1.merge(store2);
console.log(store1.getContent());
// ->
[
{
pets: {
felix: {color: 'tan', species: 'cat'},
fido: {color: 'brown', species: 'dog'},
},
},
{},
];
console.log(store2.getContent());
// ->
[
{
pets: {
felix: {color: 'tan', species: 'cat'},
fido: {color: 'brown', species: 'dog'},
},
},
{},
];
console.log(store2.getMergeableContent());
// ->
[
[
{
pets: [
{
felix: [
{
color: ['tan', 'Nn1JUF----0CnH-J', 2576658292],
species: ['cat', 'Nn1JUF-----CnH-J', 3409607562],
},
'',
4146239216,
],
fido: [
{
color: ['brown', 'Nn1JUF----0FnHIC', 1240535355],
species: ['dog', 'Nn1JUF-----FnHIC', 290599168],
},
'',
3989065420,
],
},
'',
4155188296,
],
},
'',
972931118,
],
[{}, '', 0],
];
Since
v5.0.0
setContent
The setContent method takes an array of two objects and sets the entire data of the Store.
setContent(content: Content | () => Content): this| Type | Description | |
|---|---|---|
content | Content | () => Content | An array containing the tabular and keyed-value data of the |
| returns | this | A reference to the |
This method will cause listeners to be called for any Table, Row, Cell, Value, or Id changes resulting from it.
Any part of the provided objects that are invalid (either according to the Tables or Values type, or because it does not match a TablesSchema or ValuesSchema associated with the Store), will be ignored silently.
Assuming that at least some of the provided Tables object or Values object is valid, any data that was already present in that part of the Store will be completely overwritten. If either object is completely invalid, no change will be made to the corresponding part of the Store.
The method returns a reference to the Store so that subsequent operations can be chained in a fluent style.
Since v5.4.2, this method can also take a function that returns the content.
Examples
This example sets the data of a Store.
import {createStore} from 'tinybase';
const store = createStore().setContent([
{pets: {fido: {species: 'dog'}}},
{open: true, employees: 3},
]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
console.log(store.getValues());
// -> {open: true, employees: 3}
This example sets the data of a Store with the results of a function.
import {createStore} from 'tinybase';
const store = createStore().setContent(() => [
{pets: {fido: {species: 'dog'}}},
{open: true, employees: 3},
]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
console.log(store.getValues());
// -> {open: true, employees: 3}
This example attempts to set the data of an existing Store with partly invalid, and then completely invalid objects.
import {createStore} from 'tinybase';
const store = createStore().setContent([
{pets: {fido: {species: 'dog'}}},
{open: true, employees: 3},
]);
store.setContent([{pets: {felix: {species: 'cat', bug: []}}}, '']);
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
console.log(store.getValues());
// -> {open: true, employees: 3}
store.setContent([{meaning: 42}]);
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
Since
v4.0.0
setDefaultContent
The setDefaultContent method sets initial content of a MergeableStore.
setDefaultContent(content: Content | () => Content): MergeableStore| Type | Description | |
|---|---|---|
content | Content | () => Content | An array containing the tabular and keyed-value data to be set, or a function that returns the array. |
| returns | MergeableStore | A reference to the |
This differs from the setMergeableContent method in that all of the metadata is initialized with a empty HLC timestamp - meaning that any changes applied to it will 'win', yet ensuring that at least default, initial data exists.
The method is generally intended to be used internally within TinyBase itself and the return type is assumed to be opaque to applications that use it.
Since v5.4.2, this method can also take a function that returns the content.
Example
This example creates a new MergeableStore with default data, and demonstrates that it is overwritten with another MergeableStore's data on merge, even if the other MergeableStore was provisioned earlier.
import {createMergeableStore} from 'tinybase';
const store1 = createMergeableStore('store1');
store1.setValues({employees: 3});
const store2 = createMergeableStore('store2');
store2.setDefaultContent([{}, {employees: 4}]);
console.log(store2.getMergeableContent());
// -> [[{}, "", 0], [{"employees": [4, "", 2414055963]}, "", 3035768673]]
store2.merge(store1);
console.log(store2.getContent());
// -> [{}, {employees: 3}]
Since
v5.0.0
setJson
The setJson method takes a string serialization of all of the Tables and Values in the Store and attempts to update them to those values.
setJson(tablesAndValuesJson: string): this| Type | Description | |
|---|---|---|
tablesAndValuesJson | string | A string serialization of all of the |
| returns | this | A reference to the |
From v3.0 onwards, the serialization should be of an array with two entries. The first is the Tables object, the second the Values. In previous versions (before the existence of the Values data structure), it was a sole object of Tables. For backwards compatibility, if a serialization of a single object is provided, it will be treated as the Tables type.
If the JSON cannot be parsed, this will fail silently. If it can be parsed, it will then be subject to the same validation rules as the setTables method (according to the Tables type, and matching any TablesSchema associated with the Store), and the setValues method (according to the Values type, and matching any ValuesSchema associated with the Store).
Examples
This example sets the tabular and keyed value contents of a Store from a serialization.
import {createStore} from 'tinybase';
const store = createStore();
store.setJson('[{"pets": {"fido": {"species": "dog"}}}, {"open": true}]');
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
console.log(store.getValues());
// -> {open: true}
This example sets the tabular contents of a Store from a legacy single-object serialization (compatible with v2.x and earlier).
import {createStore} from 'tinybase';
const store = createStore();
store.setJson('{"pets": {"fido": {"species": "dog"}}}');
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
console.log(store.getValues());
// -> {}
This example attempts to set both the tabular and keyed value contents of a Store from an invalid serialization.
import {createStore} from 'tinybase';
const store = createStore();
store.setValuesJson('[{"pets": {"fido": {"species": "do');
console.log(store.getTables());
// -> {}
console.log(store.getValues());
// -> {}
Since
v1.0.0
setMergeableContent
The setMergeableContent method sets the full content of a MergeableStore, together with the metadata required to make it mergeable with another.
setMergeableContent(mergeableContent: MergeableContent): MergeableStore| Type | Description | |
|---|---|---|
mergeableContent | MergeableContent | The full content and metadata of a |
| returns | MergeableStore | A reference to the |
The method is generally intended to be used internally within TinyBase itself and the return type is assumed to be opaque to applications that use it.
Example
This example creates a new MergeableStore and initializes it with the content and metadata from another.
import {createMergeableStore} from 'tinybase';
const store1 = createMergeableStore('store1');
store1.setValues({employees: 3});
console.log(store1.getMergeableContent());
// ->
[
[{}, '', 0],
[{employees: [3, 'Nn1JUF-----FnHIC', 1940815977]}, '', 1260895905],
];
const store2 = createMergeableStore('store2');
store2.setMergeableContent(store1.getMergeableContent());
console.log(store2.getMergeableContent());
// ->
[
[{}, '', 0],
[{employees: [3, 'Nn1JUF-----FnHIC', 1940815977]}, '', 1260895905],
];
Since
v5.0.0
setSchema
The setSchema method lets you specify the TablesSchema and ValuesSchema of the Store.
setSchema(
tablesSchema: TablesSchema,
valuesSchema?: ValuesSchema,
): this| Type | Description | |
|---|---|---|
tablesSchema | TablesSchema | The |
valuesSchema? | ValuesSchema | The |
| returns | this | A reference to the |
Note that this may result in a change to data in the Store, as defaults are applied or as invalid Table, Row, Cell, or Value objects are removed. These changes will fire any listeners to that data, as expected.
From v3.0 onwards, this method takes two arguments. The first is the TablesSchema object, the second the ValuesSchema. In previous versions (before the existence of the ValuesSchema data structure), only the first was present. For backwards compatibility the new second parameter is optional.
Examples
This example sets the TablesSchema and ValuesSchema of a Store after it has been created.
import {createStore} from 'tinybase';
const store = createStore().setSchema(
{
pets: {
species: {type: 'string'},
sold: {type: 'boolean', default: false},
},
},
{open: {type: 'boolean', default: false}},
);
store.addRow('pets', {species: 'dog', color: 'brown', sold: 'maybe'});
store.setValue('open', 'maybe');
console.log(store.getTables());
// -> {pets: {0: {species: 'dog', sold: false}}}
console.log(store.getValues());
// -> {open: false}
This example sets just the TablesSchema of a Store after it has been created.
import {createStore} from 'tinybase';
const store = createStore().setSchema({
pets: {
species: {type: 'string'},
sold: {type: 'boolean', default: false},
},
});
store.addRow('pets', {species: 'dog', color: 'brown', sold: 'maybe'});
console.log(store.getTables());
// -> {pets: {0: {species: 'dog', sold: false}}}
Since
v1.0.0
Listener methods
addHasTablesListener
The addHasTablesListener method registers a listener function with the Store that will be called when Tables as a whole are added to or removed from the Store.
addHasTablesListener(
listener: HasTablesListener<MergeableStore>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
listener | HasTablesListener<MergeableStore> | The function that will be called whenever |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a HasTablesListener function, and will be called with a reference to the Store. It is also given a flag to indicate whether Tables now exist (having not done previously), or do not (having done so previously).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to Tables being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasTablesListener((store, hasTables) => {
console.log('Tables ' + (hasTables ? 'added' : 'removed'));
});
store.delTables();
// -> 'Tables removed'
store.setTables({species: {dog: {price: 5}}});
// -> 'Tables added'
store.delListener(listenerId);
This example registers a listener that responds to Tables being added or removed, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore();
const listenerId = store.addHasTablesListener(
(store, hasTables) => store.setValue('hasTables', hasTables),
true,
);
store.setTables({species: {dog: {price: 5}}});
console.log(store.getValues());
// -> {hasTables: true}
store.delListener(listenerId);
Since
v4.4.0
addTablesListener
The addTablesListener method registers a listener function with the Store that will be called whenever data in the Store changes.
addTablesListener(
listener: TablesListener<MergeableStore>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
listener | TablesListener<MergeableStore> | The function that will be called whenever data in the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a TablesListener function, and will be called with a reference to the Store and a GetCellChange function in case you need to inspect any changes that occurred.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any changes to the whole Store.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTablesListener((store, getCellChange) => {
console.log('Tables changed');
console.log(getCellChange('pets', 'fido', 'color'));
});
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Tables changed'
// -> [true, 'brown', 'walnut']
store.delListener(listenerId);
This example registers a listener that responds to any changes to the whole Store, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTablesListener(
(store) => store.setCell('meta', 'update', 'store', true),
true,
);
store.delCell('pets', 'fido', 'color');
console.log(store.getTable('meta'));
// -> {update: {store: true}}
store.delListener(listenerId);
Since
v1.0.0
addTableIdsListener
The addTableIdsListener method registers a listener function with the Store that will be called whenever the Table Ids in the Store change.
addTableIdsListener(
listener: TableIdsListener<MergeableStore>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
listener | TableIdsListener<MergeableStore> | The function that will be called whenever the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a TableIdsListener function, and will be called with a reference to the Store.
By default, such a listener is only called when a Table is added or removed. To listen to all changes in the Store, use the addTablesListener method.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any change to the Table Ids.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addTableIdsListener((store) => {
console.log('Table Ids changed');
console.log(store.getTableIds());
});
store.setTable('species', {dog: {price: 5}});
// -> 'Table Ids changed'
// -> ['pets', 'species']
store.delListener(listenerId);
This example registers a listener that responds to any change to the Table Ids, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addTableIdsListener(
(store) => store.setCell('meta', 'update', 'store', true),
true, // mutator
);
store.setTable('species', {dog: {price: 5}});
console.log(store.getTable('meta'));
// -> {update: {store: true}}
store.delListener(listenerId);
Since
v1.0.0
addHasTableCellListener
The addHasTableCellListener method registers a listener function with the Store that will be called when a Cell is added to or removed from anywhere in a Table as a whole.
addHasTableCellListener(
tableId: IdOrNull,
cellId: IdOrNull,
listener: HasTableCellListener<MergeableStore>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
cellId | IdOrNull | |
listener | HasTableCellListener<MergeableStore> | The function that will be called whenever the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a HasTableCellListener function, and will be called with a reference to the Store, the Id of the Table that changed, and the Id of the Table Cell that changed. It is also given a flag to indicate whether the Cell now exists anywhere in the Table (having not done previously), or does not (having done so previously).
You can either listen to a single Table Cell being added or removed (by specifying the Table Id and Cell Id, as the method's first two parameters) or changes to any Table Cell (by providing null wildcards).
Both, either, or neither of the tableId and cellId parameters can be wildcarded with null. You can listen to a specific Row in a specific Table, any Row in a specific Table, a specific Row in any Table, or any Row in any Table.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to a specific Cell being added to or removed from the Table as a whole.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasTableCellListener(
'pets',
'color',
(store, tableId, cellId, hasTableCell) => {
console.log(
'color cell in pets table ' + (hasTableCell ? 'added' : 'removed'),
);
},
);
store.delCell('pets', 'fido', 'color');
// -> 'color cell in pets table removed'
store.setRow('pets', 'felix', {species: 'cat', color: 'brown'});
// -> 'color cell in pets table added'
store.delListener(listenerId);
This example registers a listener that responds to any Cell being added to or removed from the Table as a whole.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasTableCellListener(
null,
null,
(store, tableId, cellId, hasTableCell) => {
console.log(
`${cellId} cell in ${tableId} table ` +
(hasTableCell ? 'added' : 'removed'),
);
},
);
store.delCell('pets', 'fido', 'color');
// -> 'color cell in pets table removed'
store.setTable('species', {dog: {price: 5}});
// -> 'price cell in species table added'
store.delListener(listenerId);
This example registers a listener that responds to a specific Cell being added or removed, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasTableCellListener(
'pets',
'color',
(store, tableId, cellId) =>
store.setCell('meta', 'update', `${tableId}_${cellId}`, true),
true,
);
store.delRow('pets', 'fido');
console.log(store.getTable('meta'));
// -> {update: {pets_color: true}}
store.delListener(listenerId);
Since
v4.4.0
addHasTableListener
The addHasTableListener method registers a listener function with the Store that will be called when a Table is added to or removed from the Store.
addHasTableListener(
tableId: IdOrNull,
listener: HasTableListener<MergeableStore>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
listener | HasTableListener<MergeableStore> | The function that will be called whenever the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a HasTableListener function, and will be called with a reference to the Store and the Id of the Table that changed. It is also given a flag to indicate whether the Table now exists (having not done previously), or does not (having done so previously).
You can either listen to a single Table being added or removed (by specifying the Table Id as the method's first parameter) or changes to any Table (by providing a null wildcard).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to a specific Table being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasTableListener(
'pets',
(store, tableId, hasTable) => {
console.log('pets table ' + (hasTable ? 'added' : 'removed'));
},
);
store.delTable('pets');
// -> 'pets table removed'
store.setTable('pets', {fido: {species: 'dog', color: 'brown'}});
// -> 'pets table added'
store.delListener(listenerId);
This example registers a listener that responds to any Table being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasTableListener(
null,
(store, tableId, hasTable) => {
console.log(`${tableId} table ` + (hasTable ? 'added' : 'removed'));
},
);
store.delTable('pets');
// -> 'pets table removed'
store.setTable('species', {dog: {price: 5}});
// -> 'species table added'
store.delListener(listenerId);
This example registers a listener that responds to a specific Table being added or removed, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasTableListener(
'pets',
(store, tableId) => store.setCell('meta', 'update', tableId, true),
true,
);
store.delTable('pets', 'fido');
console.log(store.getTable('meta'));
// -> {update: {pets: true}}
store.delListener(listenerId);
Since
v4.4.0
addTableCellIdsListener
The addTableCellIdsListener method registers a listener function with the Store that will be called whenever the Cell Ids that appear anywhere in a Table change.
addTableCellIdsListener(
tableId: IdOrNull,
listener: TableCellIdsListener<MergeableStore>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
listener | TableCellIdsListener<MergeableStore> | The function that will be called whenever the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a TableCellIdsListener function, and will be called with a reference to the Store and the Id of the Table that changed.
By default, such a listener is only called when a Cell Id is added or removed from the whole of the Table. To listen to all changes in the Table, use the addTableListener method.
You can either listen to a single Table (by specifying its Id as the method's first parameter) or changes to any Table (by providing a null wildcard).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any change to the Cell Ids that appear anywhere in a Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTableCellIdsListener('pets', (store) => {
console.log('Cell Ids in pets table changed');
console.log(store.getTableCellIds('pets'));
});
store.setRow('pets', 'felix', {species: 'cat', legs: 4});
// -> 'Cell Ids in pets table changed'
// -> ['species', 'color', 'legs']
store.delListener(listenerId);
This example registers a listener that responds to any change to the Cell Ids that appear anywhere in any Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
species: {dog: {price: 5}},
});
const listenerId = store.addTableCellIdsListener(
null,
(store, tableId) => {
console.log(`Cell Ids in ${tableId} table changed`);
console.log(store.getTableCellIds(tableId));
},
);
store.setRow('pets', 'felix', {species: 'cat', legs: 4});
// -> 'Cell Ids in pets table changed'
// -> ['species', 'color', 'legs']
store.setRow('species', 'cat', {price: 4, friendly: true});
// -> 'Cell Ids in species table changed'
// -> ['price', 'friendly']
store.delListener(listenerId);
This example registers a listener that responds to the Cell Ids that appear anywhere in a Table, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTableCellIdsListener(
'pets',
(store, tableId) => store.setCell('meta', 'update', tableId, true),
true, // mutator
);
store.setRow('pets', 'felix', {species: 'cat', legs: 4});
console.log(store.getTable('meta'));
// -> {update: {pets: true}}
store.delListener(listenerId);
Since
v1.0.0
addTableListener
The addTableListener method registers a listener function with the Store that will be called whenever data in a Table changes.
addTableListener(
tableId: IdOrNull,
listener: TableListener<MergeableStore>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
listener | TableListener<MergeableStore> | The function that will be called whenever data in the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a TableListener function, and will be called with a reference to the Store, the Id of the Table that changed, and a GetCellChange function in case you need to inspect any changes that occurred.
You can either listen to a single Table (by specifying its Id as the method's first parameter) or changes to any Table (by providing a null wildcard).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any changes to a specific Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTableListener(
'pets',
(store, tableId, getCellChange) => {
console.log('pets table changed');
console.log(getCellChange('pets', 'fido', 'color'));
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'pets table changed'
// -> [true, 'brown', 'walnut']
store.delListener(listenerId);
This example registers a listener that responds to any changes to any Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTableListener(null, (store, tableId) => {
console.log(`${tableId} table changed`);
});
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'pets table changed'
store.setTable('species', {dog: {price: 5}});
// -> 'species table changed'
store.delListener(listenerId);
This example registers a listener that responds to any changes to a specific Table, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTableListener(
'pets',
(store, tableId) => store.setCell('meta', 'update', tableId, true),
true,
);
store.delCell('pets', 'fido', 'color');
console.log(store.getTable('meta'));
// -> {update: {pets: true}}
store.delListener(listenerId);
Since
v1.0.0
addRowIdsListener
The addRowIdsListener method registers a listener function with the Store that will be called whenever the Row Ids in a Table change.
addRowIdsListener(
tableId: IdOrNull,
listener: RowIdsListener<MergeableStore>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
listener | RowIdsListener<MergeableStore> | The function that will be called whenever the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a RowIdsListener function, and will be called with a reference to the Store and the Id of the Table that changed.
By default, such a listener is only called when a Row is added or removed. To listen to all changes in the Table, use the addTableListener method.
You can either listen to a single Table (by specifying its Id as the method's first parameter) or changes to any Table (by providing a null wildcard).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any change to the Row Ids of a specific Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addRowIdsListener('pets', (store) => {
console.log('Row Ids for pets table changed');
console.log(store.getRowIds('pets'));
});
store.setRow('pets', 'felix', {species: 'cat'});
// -> 'Row Ids for pets table changed'
// -> ['fido', 'felix']
store.delListener(listenerId);
This example registers a listener that responds to any change to the Row Ids of any Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addRowIdsListener(null, (store, tableId) => {
console.log(`Row Ids for ${tableId} table changed`);
console.log(store.getRowIds(tableId));
});
store.setRow('pets', 'felix', {species: 'cat'});
// -> 'Row Ids for pets table changed'
// -> ['fido', 'felix']
store.setRow('species', 'dog', {price: 5});
// -> 'Row Ids for species table changed'
// -> ['dog']
store.delListener(listenerId);
This example registers a listener that responds to any change to the Row Ids of a specific Table, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addRowIdsListener(
'pets',
(store, tableId) => store.setCell('meta', 'update', tableId, true),
true, // mutator
);
store.setRow('pets', 'felix', {species: 'cat'});
console.log(store.getTable('meta'));
// -> {update: {pets: true}}
store.delListener(listenerId);
Since
v1.0.0
addSortedRowIdsListener
The addSortedRowIdsListener method registers a listener function with the Store that will be called whenever sorted (and optionally, paginated) Row Ids in a Table change.
addSortedRowIdsListener(
tableId: string,
cellId: undefined | string,
descending: boolean,
offset: number,
limit: undefined | number,
listener: SortedRowIdsListener<MergeableStore>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
tableId | string | |
cellId | undefined | string | The |
descending | boolean | Whether the sorting should be in descending order. |
offset | number | The number of |
limit | undefined | number | The maximum number of |
listener | SortedRowIdsListener<MergeableStore> | The function that will be called whenever the sorted |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a SortedRowIdsListener function, and will be called with a reference to the Store, the Id of the Table whose Row Ids sorting changed, the Cell Id being used to sort them, whether descending or not, and the offset and limit of the number of Ids returned, for pagination purposes. It also receives the sorted array of Ids itself, so that you can use them in the listener without the additional cost of an explicit call to getSortedRowIds.
Such a listener is called when a Row is added or removed, but also when a value in the specified Cell (somewhere in the Table) has changed enough to change the sorting of the Row Ids.
Unlike most other listeners, you cannot provide wildcards (due to the cost of detecting changes to the sorting). You can only listen to a single specified Table, sorted by a single specified Cell.
The sorting of the rows is alphanumeric, and you can indicate whether it should be in descending order. The offset and limit parameters are used to paginate results, but default to 0 and undefined to return all available Row Ids if not specified.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any change to the sorted Row Ids of a specific Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
cujo: {species: 'wolf'},
felix: {species: 'cat'},
},
});
console.log(store.getSortedRowIds('pets', 'species', false));
// -> ['felix', 'cujo']
const listenerId = store.addSortedRowIdsListener(
'pets',
'species',
false,
0,
undefined,
(store, tableId, cellId, descending, offset, limit, sortedRowIds) => {
console.log(`Sorted Row Ids for ${tableId} table changed`);
console.log(sortedRowIds);
// ^ cheaper than calling getSortedRowIds again
},
);
store.setRow('pets', 'fido', {species: 'dog'});
// -> 'Sorted Row Ids for pets table changed'
// -> ['felix', 'fido', 'cujo']
store.delListener(listenerId);
This example registers a listener that responds to any change to a paginated section of the sorted Row Ids of a specific Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {price: 6},
felix: {price: 5},
mickey: {price: 2},
tom: {price: 4},
carnaby: {price: 3},
lowly: {price: 1},
},
});
const listenerId = store.addSortedRowIdsListener(
'pets',
'price',
false,
0,
3,
(store, tableId, cellId, descending, offset, limit, sortedRowIds) => {
console.log(`First three sorted Row Ids for ${tableId} table changed`);
console.log(sortedRowIds);
// ^ cheaper than calling getSortedRowIds again
},
);
console.log(store.getSortedRowIds('pets', 'price', false, 0, 3));
// -> ['lowly', 'mickey', 'carnaby']
store.setCell('pets', 'carnaby', 'price', 4.5);
// -> 'First three sorted Row Ids for pets table changed'
// -> ['lowly', 'mickey', 'tom']
store.delListener(listenerId);
This example registers a listener that responds to any change to the sorted Row Ids of a specific Table. The Row Ids are sorted by their own value, since the cellId parameter is explicitly undefined.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
console.log(store.getSortedRowIds('pets', undefined, false));
// -> ['felix', 'fido']
const listenerId = store.addSortedRowIdsListener(
'pets',
undefined,
false,
0,
undefined,
(store, tableId, cellId, descending, offset, limit, sortedRowIds) => {
console.log(`Sorted Row Ids for ${tableId} table changed`);
console.log(sortedRowIds);
// ^ cheaper than calling getSortedRowIds again
},
);
store.setRow('pets', 'cujo', {species: 'wolf'});
// -> 'Sorted Row Ids for pets table changed'
// -> ['cujo', 'felix', 'fido']
store.delListener(listenerId);
This example registers a listener that responds to a change in the sorting of the rows of a specific Table, even though the set of Ids themselves has not changed.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
console.log(store.getSortedRowIds('pets', 'species', false));
// -> ['felix', 'fido']
const listenerId = store.addSortedRowIdsListener(
'pets',
'species',
false,
0,
undefined,
(store, tableId, cellId, descending, offset, limit, sortedRowIds) => {
console.log(`Sorted Row Ids for ${tableId} table changed`);
console.log(sortedRowIds);
// ^ cheaper than calling getSortedRowIds again
},
);
store.setCell('pets', 'felix', 'species', 'tiger');
// -> 'Sorted Row Ids for pets table changed'
// -> ['fido', 'felix']
store.delListener(listenerId);
This example registers a listener that responds to any change to the sorted Row Ids of a specific Table, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
cujo: {species: 'wolf'},
felix: {species: 'cat'},
},
});
const listenerId = store.addSortedRowIdsListener(
'pets',
'species',
false,
0,
undefined,
(store, tableId) => store.setCell('meta', 'sorted', tableId, true),
true, // mutator
);
store.setRow('pets', 'fido', {species: 'dog'});
console.log(store.getTable('meta'));
// -> {sorted: {pets: true}}
store.delListener(listenerId);
Since
v2.0.0
When called with an object as the first argument, the addSortedRowIdsListener method destructures it to make it easier to skip optional parameters.
addSortedRowIdsListener(
args: SortedRowIdsArgs,
listener: SortedRowIdsListener<MergeableStore>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
args | SortedRowIdsArgs | A |
listener | SortedRowIdsListener<MergeableStore> | The function that will be called whenever the sorted |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
Example
This example registers a listener that responds to any change to the first of the sorted Row Ids of a specific Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {price: 6}, felix: {price: 5}},
});
const listenerId = store.addSortedRowIdsListener(
{tableId: 'pets', limit: 1},
(store, tableId, cellId, descending, offset, limit, sortedRowIds) => {
console.log(`First sorted Row Id for ${tableId} table changed`);
console.log(sortedRowIds);
// ^ cheaper than calling getSortedRowIds again
},
);
console.log(store.getSortedRowIds({tableId: 'pets', limit: 1}));
// -> ['felix']
store.setRow('pets', 'carnaby', {price: 4.5});
// -> 'First sorted Row Id for pets table changed'
// -> ['carnaby']
store.delListener(listenerId);
Since
v6.1.0
addHasRowListener
The addHasRowListener method registers a listener function with the Store that will be called when a Row is added to or removed from the Store.
addHasRowListener(
tableId: IdOrNull,
rowId: IdOrNull,
listener: HasRowListener<MergeableStore>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
listener | HasRowListener<MergeableStore> | The function that will be called whenever the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a HasRowListener function, and will be called with a reference to the Store, the Id of the Table that changed, and the Id of the Row that changed. It is also given a flag to indicate whether the Row now exists (having not done previously), or does not (having done so previously).
You can either listen to a single Row being added or removed (by specifying the Table Id and Row Id, as the method's first two parameters) or changes to any Row (by providing null wildcards).
Both, either, or neither of the tableId and rowId parameters can be wildcarded with null. You can listen to a specific Row in a specific Table, any Row in a specific Table, a specific Row in any Table, or any Row in any Table.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to a specific Row being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasRowListener(
'pets',
'fido',
(store, tableId, rowId, hasRow) => {
console.log(
'fido row in pets table ' + (hasRow ? 'added' : 'removed'),
);
},
);
store.delRow('pets', 'fido');
// -> 'fido row in pets table removed'
store.setRow('pets', 'fido', {species: 'dog', color: 'brown'});
// -> 'fido row in pets table added'
store.delListener(listenerId);
This example registers a listener that responds to any Row being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasRowListener(
null,
null,
(store, tableId, rowId, hasRow) => {
console.log(
`${rowId} row in ${tableId} table ` + (hasRow ? 'added' : 'removed'),
);
},
);
store.delRow('pets', 'fido');
// -> 'fido row in pets table removed'
store.setTable('species', {dog: {price: 5}});
// -> 'dog row in species table added'
store.delListener(listenerId);
This example registers a listener that responds to a specific Row being added or removed, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasRowListener(
'pets',
'fido',
(store, tableId, rowId) =>
store.setCell('meta', 'update', `${tableId}_${rowId}`, true),
true,
);
store.delRow('pets', 'fido');
console.log(store.getTable('meta'));
// -> {update: {pets_fido: true}}
store.delListener(listenerId);
Since
v4.4.0
addRowCountListener
The addRowCountListener method registers a listener function with the Store that will be called whenever the count of Row objects in a Table change.
addRowCountListener(
tableId: IdOrNull,
listener: RowCountListener<MergeableStore>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
listener | RowCountListener<MergeableStore> | The function that will be called whenever the number of |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a RowCountListener function, and will be called with a reference to the Store, the Id of the Table that changed, and the number of Row objects in the Table.
You can either listen to a single Table (by specifying its Id as the method's first parameter) or changes to any Table (by providing a null wildcard).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to a change in the number of Row objects in a specific Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addRowCountListener(
'pets',
(store, _tableId, count) => {
console.log('Row count for pets table changed to ' + count);
},
);
store.setRow('pets', 'felix', {species: 'cat'});
// -> 'Row count for pets table changed to 2'
store.delListener(listenerId);
This example registers a listener that responds to any change to a change in the number of Row objects of any Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addRowCountListener(
null,
(store, tableId, count) => {
console.log(`Row count for ${tableId} table changed to ${count}`);
},
);
store.setRow('pets', 'felix', {species: 'cat'});
// -> 'Row count for pets table changed to 2'
store.setRow('species', 'dog', {price: 5});
// -> 'Row count for species table changed to 1'
store.delListener(listenerId);
This example registers a listener that responds to any change to a change in the number of Row objects of a specific Table, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addRowCountListener(
'pets',
(store, tableId, count) =>
store.setCell('meta', 'update', tableId, count),
true, // mutator
);
store.setRow('pets', 'felix', {species: 'cat'});
console.log(store.getTable('meta'));
// -> {update: {pets: 2}}
store.delListener(listenerId);
Since
v4.1.0
addRowListener
The addRowListener method registers a listener function with the Store that will be called whenever data in a Row changes.
addRowListener(
tableId: IdOrNull,
rowId: IdOrNull,
listener: RowListener<MergeableStore>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
listener | RowListener<MergeableStore> | The function that will be called whenever data in the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a RowListener function, and will be called with a reference to the Store, the Id of the Table that changed, the Id of the Row that changed, and a GetCellChange function in case you need to inspect any changes that occurred.
You can either listen to a single Row (by specifying the Table Id and Row Id as the method's first two parameters) or changes to any Row (by providing null wildcards).
Both, either, or neither of the tableId and rowId parameters can be wildcarded with null. You can listen to a specific Row in a specific Table, any Row in a specific Table, a specific Row in any Table, or any Row in any Table.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any changes to a specific Row.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addRowListener(
'pets',
'fido',
(store, tableId, rowId, getCellChange) => {
console.log('fido row in pets table changed');
console.log(getCellChange('pets', 'fido', 'color'));
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'fido row in pets table changed'
// -> [true, 'brown', 'walnut']
store.delListener(listenerId);
This example registers a listener that responds to any changes to any Row.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addRowListener(
null,
null,
(store, tableId, rowId) => {
console.log(`${rowId} row in ${tableId} table changed`);
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'fido row in pets table changed'
store.setTable('species', {dog: {price: 5}});
// -> 'dog row in species table changed'
store.delListener(listenerId);
This example registers a listener that responds to any changes to a specific Row, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addRowListener(
'pets',
'fido',
(store, tableId, rowId) =>
store.setCell('meta', 'update', `${tableId}_${rowId}`, true),
true,
);
store.delCell('pets', 'fido', 'color');
console.log(store.getTable('meta'));
// -> {update: {pets_fido: true}}
store.delListener(listenerId);
Since
v1.0.0
addCellIdsListener
The addCellIdsListener method registers a listener function with the Store that will be called whenever the Cell Ids in a Row change.
addCellIdsListener(
tableId: IdOrNull,
rowId: IdOrNull,
listener: CellIdsListener<MergeableStore>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
listener | CellIdsListener<MergeableStore> | The function that will be called whenever the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a CellIdsListener function, and will be called with a reference to the Store, the Id of the Table, and the Id of the Row that changed.
By default, such a listener is only called when a Cell is added or removed. To listen to all changes in the Row, use the addRowListener method.
You can either listen to a single Row (by specifying the Table Id and Row Id as the method's first two parameters) or changes to any Row (by providing a null wildcard).
Both, either, or neither of the tableId and rowId parameters can be wildcarded with null. You can listen to a specific Row in a specific Table, any Row in a specific Table, a specific Row in any Table, or any Row in any Table.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any change to the Cell Ids of a specific Row.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addCellIdsListener('pets', 'fido', (store) => {
console.log('Cell Ids for fido row in pets table changed');
console.log(store.getCellIds('pets', 'fido'));
});
store.setCell('pets', 'fido', 'color', 'brown');
// -> 'Cell Ids for fido row in pets table changed'
// -> ['species', 'color']
store.delListener(listenerId);
This example registers a listener that responds to any change to the Cell Ids of any Row.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addCellIdsListener(
null,
null,
(store, tableId, rowId) => {
console.log(`Cell Ids for ${rowId} row in ${tableId} table changed`);
console.log(store.getCellIds(tableId, rowId));
},
);
store.setCell('pets', 'fido', 'color', 'brown');
// -> 'Cell Ids for fido row in pets table changed'
// -> ['species', 'color']
store.setCell('species', 'dog', 'price', 5);
// -> 'Cell Ids for dog row in species table changed'
// -> ['price']
store.delListener(listenerId);
This example registers a listener that responds to any change to the Cell Ids of a specific Row, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addCellIdsListener(
'pets',
'fido',
(store, tableId, rowId) =>
store.setCell('meta', 'update', `${tableId}_${rowId}`, true),
true, // mutator
);
store.setCell('pets', 'fido', 'color', 'brown');
console.log(store.getTable('meta'));
// -> {update: {pets_fido: true}}
store.delListener(listenerId);
Since
v1.0.0
addCellListener
The addCellListener method registers a listener function with the Store that will be called whenever data in a Cell changes.
addCellListener(
tableId: IdOrNull,
rowId: IdOrNull,
cellId: IdOrNull,
listener: CellListener<MergeableStore>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
cellId | IdOrNull | |
listener | CellListener<MergeableStore> | The function that will be called whenever data in the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a CellListener function, and will be called with a reference to the Store, the Id of the Table that changed, the Id of the Row that changed, the Id of the Cell that changed, the new Cell value, the old Cell value, and a GetCellChange function in case you need to inspect any changes that occurred.
You can either listen to a single Cell (by specifying the Table Id, Row Id, and Cell Id as the method's first three parameters) or changes to any Cell (by providing null wildcards).
All, some, or none of the tableId, rowId, and cellId parameters can be wildcarded with null. You can listen to a specific Cell in a specific Row in a specific Table, any Cell in any Row in any Table, for example - or every other combination of wildcards.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any changes to a specific Cell.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId, newCell, oldCell, getCellChange) => {
console.log('color cell in fido row in pets table changed');
console.log([oldCell, newCell]);
console.log(getCellChange('pets', 'fido', 'color'));
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'color cell in fido row in pets table changed'
// -> ['brown', 'walnut']
// -> [true, 'brown', 'walnut']
store.delListener(listenerId);
This example registers a listener that responds to any changes to any Cell.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addCellListener(
null,
null,
null,
(store, tableId, rowId, cellId) => {
console.log(
`${cellId} cell in ${rowId} row in ${tableId} table changed`,
);
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'color cell in fido row in pets table changed'
store.setTable('species', {dog: {price: 5}});
// -> 'price cell in dog row in species table changed'
store.delListener(listenerId);
This example registers a listener that responds to any changes to a specific Cell, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId) =>
store.setCell('meta', 'update', `${tableId}_${rowId}_${cellId}`, true),
true,
);
store.delCell('pets', 'fido', 'color');
console.log(store.getTable('meta'));
// -> {update: {pets_fido_color: true}}
store.delListener(listenerId);
Since
v1.0.0
addHasCellListener
The addHasCellListener method registers a listener function with the Store that will be called when a Cell is added to or removed from the Store.
addHasCellListener(
tableId: IdOrNull,
rowId: IdOrNull,
cellId: IdOrNull,
listener: HasCellListener<MergeableStore>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
cellId | IdOrNull | |
listener | HasCellListener<MergeableStore> | The function that will be called whenever the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a HasCellListener function, and will be called with a reference to the Store, the Id of the Table that changed, the Id of the Row that changed, and the Id of the Cell that changed. It is also given a flag to indicate whether the Cell now exists (having not done previously), or does not (having done so previously).
You can either listen to a single Cell being added or removed (by specifying the Table Id, Row Id, and Cell Id as the method's first three parameters) or changes to any Cell (by providing null wildcards).
All, some, or none of the tableId, rowId, and cellId parameters can be wildcarded with null. You can listen to a specific Cell in a specific Row in a specific Table, any Cell in any Row in any Table, for example - or every other combination of wildcards.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to a specific Cell being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId, hasCell) => {
console.log(
'color cell in fido row in pets table ' +
(hasCell ? 'added' : 'removed'),
);
},
);
store.delCell('pets', 'fido', 'color');
// -> 'color cell in fido row in pets table removed'
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'color cell in fido row in pets table added'
store.delListener(listenerId);
This example registers a listener that responds to any Cell being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasCellListener(
null,
null,
null,
(store, tableId, rowId, cellId, hasCell) => {
console.log(
`${cellId} cell in ${rowId} row in ${tableId} table ` +
(hasCell ? 'added' : 'removed'),
);
},
);
store.delCell('pets', 'fido', 'color');
// -> 'color cell in fido row in pets table removed'
store.setTable('species', {dog: {price: 5}});
// -> 'price cell in dog row in species table added'
store.delListener(listenerId);
This example registers a listener that responds to a specific Cell being added or removed, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId) =>
store.setCell('meta', 'update', `${tableId}_${rowId}_${cellId}`, true),
true,
);
store.delCell('pets', 'fido', 'color');
console.log(store.getTable('meta'));
// -> {update: {pets_fido_color: true}}
store.delListener(listenerId);
Since
v4.4.0
addInvalidCellListener
The addInvalidCellListener method registers a listener function with the Store that will be called whenever invalid data was attempted to be written to a Cell.
addInvalidCellListener(
tableId: IdOrNull,
rowId: IdOrNull,
cellId: IdOrNull,
listener: InvalidCellListener<MergeableStore>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
cellId | IdOrNull | |
listener | InvalidCellListener<MergeableStore> | The function that will be called whenever an attempt to write invalid data to the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is an InvalidCellListener function, and will be called with a reference to the Store, the Id of the Table, the Id of the Row, and the Id of Cell that was being attempted to be changed. It is also given the invalid value of the Cell, which could have been of absolutely any type. Since there could have been multiple failed attempts to set the Cell within a single transaction, this is an array containing each attempt, chronologically.
You can either listen to a single Cell (by specifying the Table Id, Row Id, and Cell Id as the method's first three parameters) or invalid attempts to change any Cell (by providing null wildcards).
All, some, or none of the tableId, rowId, and cellId parameters can be wildcarded with null. You can listen to a specific Cell in a specific Row in a specific Table, any Cell in any Row in any Table, for example - or every other combination of wildcards.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Special note should be made for how the listener will be called when a TablesSchema is present. The listener will be called:
- if a
Tableis being updated that is not specified in theTablesSchema, - if a
Cellis of the wrong type specified in theTablesSchema, - if a
Cellis omitted and is not defaulted in theTablesSchema, - or if an empty
Rowis provided and there are noCelldefaults in theTablesSchema.
The listener will not be called if a Cell that is defaulted in the TablesSchema is not provided, as long as all of the Cells that are not defaulted are provided.
To help understand all of these schema-based conditions, please see the TablesSchema example below.
Examples
This example registers a listener that responds to any invalid changes to a specific Cell.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addInvalidCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId, invalidCells) => {
console.log('Invalid color cell in fido row in pets table');
console.log(invalidCells);
},
);
store.setCell('pets', 'fido', 'color', {r: '96', g: '4B', b: '00'});
// -> 'Invalid color cell in fido row in pets table'
// -> [{r: '96', g: '4B', b: '00'}]
store.delListener(listenerId);
This example registers a listener that responds to any invalid changes to any Cell - in a Store without a TablesSchema. Note also how it then responds to cases where empty or invalid Row objects, or Table objects, or Tables objects are provided.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addInvalidCellListener(
null,
null,
null,
(store, tableId, rowId, cellId) => {
console.log(
`Invalid ${cellId} cell in ${rowId} row in ${tableId} table`,
);
},
);
store.setCell('pets', 'fido', 'color', {r: '96', g: '4B', b: '00'});
// -> 'Invalid color cell in fido row in pets table'
store.setTable('sales', {fido: {date: new Date()}});
// -> 'Invalid date cell in fido row in sales table'
store.setRow('pets', 'felix', {});
// -> 'Invalid undefined cell in felix row in pets table'
store.setRow('filter', 'name', /[a-z]?/);
// -> 'Invalid undefined cell in name row in filter table'
store.setRow('sales', '2021', {forecast: undefined});
// -> 'Invalid forecast cell in 2021 row in sales table'
store.addRow('filter', /[0-9]?/);
// -> 'Invalid undefined cell in undefined row in filter table'
store.setTable('raw', {});
// -> 'Invalid undefined cell in undefined row in raw table'
store.setTable('raw', ['row1', 'row2']);
// -> 'Invalid undefined cell in undefined row in raw table'
store.setTables(['table1', 'table2']);
// -> 'Invalid undefined cell in undefined row in undefined table'
store.delListener(listenerId);
This example registers a listener that responds to any invalid changes to any Cell - in a Store with a TablesSchema. Note how it responds to cases where missing parameters are provided for optional, and defaulted Cell values in a Row.
import {createStore} from 'tinybase';
const store = createStore().setTablesSchema({
pets: {
species: {type: 'string'},
color: {type: 'string', default: 'unknown'},
},
});
const listenerId = store.addInvalidCellListener(
null,
null,
null,
(store, tableId, rowId, cellId) => {
console.log(
`Invalid ${cellId} cell in ${rowId} row in ${tableId} table`,
);
},
);
store.setRow('sales', 'fido', {price: 5});
// -> 'Invalid price cell in fido row in sales table'
// The listener is called, because the sales Table is not in the schema
store.setRow('pets', 'felix', {species: true});
// -> 'Invalid species cell in felix row in pets table'
// The listener is called, because species is invalid...
console.log(store.getRow('pets', 'felix'));
// -> {color: 'unknown'}
// ...even though a Row was set with the default value
store.setRow('pets', 'fido', {color: 'brown'});
// -> 'Invalid species cell in fido row in pets table'
// The listener is called, because species is missing and not defaulted...
console.log(store.getRow('pets', 'fido'));
// -> {color: 'brown'}
// ...even though a Row was set
store.setRow('pets', 'rex', {species: 'dog'});
console.log(store.getRow('pets', 'rex'));
// -> {species: 'dog', color: 'unknown'}
// The listener is not called, because color is defaulted
store.delTables().setTablesSchema({
pets: {
species: {type: 'string'},
color: {type: 'string'},
},
});
store.setRow('pets', 'cujo', {});
// -> 'Invalid species cell in cujo row in pets table'
// -> 'Invalid color cell in cujo row in pets table'
// -> 'Invalid undefined cell in cujo row in pets table'
// The listener is called multiple times, because neither Cell is defaulted
// and the Row as a whole is empty
store.delListener(listenerId);
This example registers a listener that responds to any changes to a specific Cell, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addInvalidCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId, invalidCells) =>
store.setCell(
'meta',
'invalid_updates',
`${tableId}_${rowId}_${cellId}`,
JSON.stringify(invalidCells[0]),
),
true,
);
store.setCell('pets', 'fido', 'color', {r: '96', g: '4B', b: '00'});
console.log(store.getRow('meta', 'invalid_updates'));
// -> {'pets_fido_color': '{"r":"96","g":"4B","b":"00"}'}
store.delListener(listenerId);
Since
v1.1.0
addHasValuesListener
The addHasValuesListener method registers a listener function with the Store that will be called when Values as a whole are added to or removed from the Store.
addHasValuesListener(
listener: HasValuesListener<MergeableStore>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
listener | HasValuesListener<MergeableStore> | The function that will be called whenever |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a HasValuesListener function, and will be called with a reference to the Store. It is also given a flag to indicate whether Values now exist (having not done previously), or do not (having done so previously).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to Values being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addHasValuesListener((store, hasValues) => {
console.log('Values ' + (hasValues ? 'added' : 'removed'));
});
store.delValues();
// -> 'Values removed'
store.setValue('employees', 4);
// -> 'Values added'
store.delListener(listenerId);
This example registers a listener that responds to Values being added or removed, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore();
const listenerId = store.addHasValuesListener(
(store, hasValues) => store.setValue('hasValues', hasValues),
true,
);
store.setValue('employees', 4);
console.log(store.getValues());
// -> {employees: 4, hasValues: true}
store.delListener(listenerId);
Since
v4.4.0
addValuesListener
The addValuesListener method registers a listener function with the Store that will be called whenever the Values change.
addValuesListener(
listener: ValuesListener<MergeableStore>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
listener | ValuesListener<MergeableStore> | The function that will be called whenever data in the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a ValuesListener function, and will be called with a reference to the Store and a GetValueChange function in case you need to inspect any changes that occurred.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any changes to the Store's Values.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addValuesListener((store, getValueChange) => {
console.log('values changed');
console.log(getValueChange('employees'));
});
store.setValue('employees', 4);
// -> 'values changed'
// -> [true, 3, 4]
store.delListener(listenerId);
This example registers a listener that responds to any changes to the Store's Values, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addValuesListener(
(store) => store.setValue('updated', true),
true,
);
store.setValue('employees', 4);
console.log(store.getValues());
// -> {open: true, employees: 4, updated: true}
store.delListener(listenerId);
Since
v3.0.0
addHasValueListener
The addHasValueListener method registers a listener function with the Store that will be called when a Value is added to or removed from the Store.
addHasValueListener(
valueId: IdOrNull,
listener: HasValueListener<MergeableStore>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
valueId | IdOrNull | |
listener | HasValueListener<MergeableStore> | The function that will be called whenever the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a HasValueListener function, and will be called with a reference to the Store and the Id of Value that changed. It is also given a flag to indicate whether the Value now exists (having not done previously), or does not (having done so previously).
You can either listen to a single Value being added or removed (by specifying the Value Id) or any Value being added or removed (by providing a null wildcard).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to a specific Value being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addHasValueListener(
'employees',
(store, valueId, hasValue) => {
console.log('employee value ' + (hasValue ? 'added' : 'removed'));
},
);
store.delValue('employees');
// -> 'employee value removed'
store.setValue('employees', 4);
// -> 'employee value added'
store.delListener(listenerId);
This example registers a listener that responds to any Value being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addHasValueListener(
null,
(store, valueId, hasValue) => {
console.log(valueId + ' value ' + (hasValue ? 'added' : 'removed'));
},
);
store.delValue('employees');
// -> 'employees value removed'
store.setValue('website', 'https://pets.com');
// -> 'website value added'
store.delListener(listenerId);
This example registers a listener that responds to a specific Value being added or removed, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addHasValueListener(
'employees',
(store) => store.setValue('updated', true),
true,
);
store.delValue('employees');
console.log(store.getValues());
// -> {open: true, updated: true}
store.delListener(listenerId);
Since
v4.4.0
addInvalidValueListener
The addInvalidValueListener method registers a listener function with the Store that will be called whenever invalid data was attempted to be written to a Value.
addInvalidValueListener(
valueId: IdOrNull,
listener: InvalidValueListener<MergeableStore>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
valueId | IdOrNull | |
listener | InvalidValueListener<MergeableStore> | The function that will be called whenever an attempt to write invalid data to the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is an InvalidValueListener function, and will be called with a reference to the Store and the Id of Value that was being attempted to be changed. It is also given the invalid value of the Value, which could have been of absolutely any type. Since there could have been multiple failed attempts to set the Value within a single transaction, this is an array containing each attempt, chronologically.
You can either listen to a single Value (by specifying the Value Id as the method's first parameter) or invalid attempts to change any Value (by providing a null wildcard).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Special note should be made for how the listener will be called when a ValuesSchema is present. The listener will be called:
- if a
Valueis being updated that is not specified in theValuesSchema, - if a
Valueis of the wrong type specified in theValuesSchema, - or if a
Valueis omitted when using setValues that is not defaulted in theValuesSchema.
The listener will not be called if a Value that is defaulted in the ValuesSchema is not provided, as long as all of the Values that are not defaulted are provided.
To help understand all of these schema-based conditions, please see the ValuesSchema example below.
Examples
This example registers a listener that responds to any invalid changes to a specific Value.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
const listenerId = store.addInvalidValueListener(
'open',
(store, valueId, invalidValues) => {
console.log('Invalid open value');
console.log(invalidValues);
},
);
store.setValue('open', {yes: true});
// -> 'Invalid open value'
// -> [{yes: true}]
store.delListener(listenerId);
This example registers a listener that responds to any invalid changes to any Value - in a Store without a ValuesSchema. Note also how it then responds to cases where an empty Values object is provided.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
const listenerId = store.addInvalidValueListener(
null,
(store, valueId) => {
console.log(`Invalid ${valueId} value`);
},
);
store.setValue('open', {yes: true});
// -> 'Invalid open value'
store.setValue('employees', ['alice', 'bob']);
// -> 'Invalid employees value'
store.setValues('pets', 'felix', {});
// -> 'Invalid undefined value'
store.delListener(listenerId);
This example registers a listener that responds to any invalid changes to any Value - in a Store with a ValuesSchema. Note how it responds to cases where missing parameters are provided for optional, and defaulted Values.
import {createStore} from 'tinybase';
const store = createStore().setValuesSchema({
open: {type: 'boolean', default: false},
employees: {type: 'number'},
});
console.log(store.getValues());
// -> {open: false}
const listenerId = store.addInvalidValueListener(
null,
(store, valueId) => {
console.log(`Invalid ${valueId} value`);
},
);
store.setValue('website', true);
// -> 'Invalid website value'
// The listener is called, because the website Value is not in the schema
store.setValue('open', 'yes');
// -> 'Invalid open value'
// The listener is called, because 'open' is invalid...
console.log(store.getValues());
// -> {open: false}
// ...even though it is still present with the default value
store.setValues({open: true});
// -> 'Invalid employees value'
// The listener is called because employees is missing and not defaulted...
console.log(store.getValues());
// -> {open: true}
// ...even though the Values were set
store.setValues({employees: 3});
console.log(store.getValues());
// -> {open: false, employees: 3}
// The listener is not called, because 'open' is defaulted
store.setValuesSchema({
open: {type: 'boolean'},
employees: {type: 'number'},
});
store.setValues({});
// -> 'Invalid open value'
// -> 'Invalid employees value'
// -> 'Invalid undefined value'
// The listener is called multiple times, because neither Value is
// defaulted and the Values as a whole were empty
store.delListener(listenerId);
This example registers a listener that responds to any changes to a specific Value, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
const listenerId = store.addInvalidValueListener(
'open',
(store, valueId, invalidValues) =>
store.setValue('invalid_updates', JSON.stringify(invalidValues[0])),
true,
);
store.setValue('open', {yes: true});
console.log(store.getValue('invalid_updates'));
// -> '{"yes":true}'
store.delListener(listenerId);
Since
v3.0.0
addValueIdsListener
The addValueIdsListener method registers a listener function with the Store that will be called whenever the Value Ids in a Store change.
addValueIdsListener(
listener: ValueIdsListener<MergeableStore>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
listener | ValueIdsListener<MergeableStore> | The function that will be called whenever the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a ValueIdsListener function, and will be called with a reference to the Store.
By default, such a listener is only called when a Value is added or removed. To listen to all changes in the Values, use the addValuesListener method.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any change to the Value Ids.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
const listenerId = store.addValueIdsListener((store) => {
console.log('Value Ids changed');
console.log(store.getValueIds());
});
store.setValue('employees', 3);
// -> 'Value Ids changed'
// -> ['open', 'employees']
store.delListener(listenerId);
This example registers a listener that responds to any change to the Value Ids, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
const listenerId = store.addValueIdsListener(
(store) => store.setValue('updated', true),
true, // mutator
);
store.setValue('employees', 3);
console.log(store.getValues());
// -> {open: true, employees: 3, updated: true}
store.delListener(listenerId);
Since
v3.0.0
addValueListener
The addValueListener method registers a listener function with the Store that will be called whenever data in a Value changes.
addValueListener(
valueId: IdOrNull,
listener: ValueListener<MergeableStore>,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
valueId | IdOrNull | |
listener | ValueListener<MergeableStore> | The function that will be called whenever data in the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a ValueListener function, and will be called with a reference to the Store, the Id of the Value that changed, the new Value value, the old Value, and a GetValueChange function in case you need to inspect any changes that occurred.
You can either listen to a single Value (by specifying the Value Id) or changes to any Value (by providing a null wildcard).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any changes to a specific Value.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addValueListener(
'employees',
(store, valueId, newValue, oldValue, getValueChange) => {
console.log('employee value changed');
console.log([oldValue, newValue]);
console.log(getValueChange('employees'));
},
);
store.setValue('employees', 4);
// -> 'employee value changed'
// -> [3, 4]
// -> [true, 3, 4]
store.delListener(listenerId);
This example registers a listener that responds to any changes to any Value.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addValueListener(null, (store, valueId) => {
console.log(`${valueId} value changed`);
});
store.setValue('employees', 4);
// -> 'employees value changed'
store.setValue('open', false);
// -> 'open value changed'
store.delListener(listenerId);
This example registers a listener that responds to any changes to a specific Value, and which also mutates the Store itself.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addValueListener(
'employees',
(store) => store.setValue('updated', true),
true,
);
store.delValue('employees');
console.log(store.getValues());
// -> {open: true, updated: true}
store.delListener(listenerId);
Since
v3.0.0
addDidFinishTransactionListener
The addDidFinishTransactionListener method registers a listener function with the Store that will be called just after other non-mutating listeners are called at the end of the transaction.
addDidFinishTransactionListener(listener: TransactionListener<MergeableStore>): string| Type | Description | |
|---|---|---|
listener | TransactionListener<MergeableStore> | The function that will be called after the end of a transaction. |
| returns | string | A unique |
This is useful if you need to know that a set of listeners have just been called at the end of a transaction, perhaps to batch their consequences together.
The provided TransactionListener will receive a reference to the Store and two booleans to indicate whether Cell or Value data has been touched during the transaction. The two flags is intended as a hint about whether non-mutating listeners might have been called at the end of the transaction.
Here, 'touched' means that Cell or Value data has either been changed, or changed and then changed back to its original value during the transaction. The exception is a transaction that has been rolled back, for which the value of cellsTouched and valuesTouched in the listener will be false because all changes have been reverted.
Note that a TransactionListener added to the Store with this method cannot mutate the Store itself, and attempts to do so will fail silently.
Example
This example registers a listener that is called at the end of the transaction, just after its listeners have been called. The transactions shown here variously change, touch, and rollback cells, demonstrating how the cellsTouched and valuesTouched parameters in the listener work.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
})
.setValues({open: true, employees: 3});
const listenerId = store.addDidFinishTransactionListener((store) => {
const [cellsTouched, valuesTouched] = store.getTransactionLog() ?? {};
console.log(`Cells/Values touched: ${cellsTouched}/${valuesTouched}`);
});
const listenerId2 = store.addTablesListener(() =>
console.log('Tables changed'),
);
const listenerId3 = store.addValuesListener(() =>
console.log('Values changed'),
);
store.transaction(() =>
store.setCell('pets', 'fido', 'color', 'brown').setValue('employees', 3),
);
// -> 'Cells/Values touched: false/false'
store.transaction(() => store.setCell('pets', 'fido', 'color', 'walnut'));
// -> 'Tables changed'
// -> 'Cells/Values touched: true/false'
store.transaction(() => store.setValue('employees', 4));
// -> 'Values changed'
// -> 'Cells/Values touched: false/true'
store.transaction(() => {
store
.setRow('pets', 'felix', {species: 'cat'})
.delRow('pets', 'felix')
.setValue('city', 'London')
.delValue('city');
});
// -> 'Cells/Values touched: true/true'
// But no Tables or Values listeners fired since there are no net changes.
store.transaction(
() =>
store
.setRow('pets', 'felix', {species: 'cat'})
.setValue('city', 'London'),
() => true,
);
// -> 'Cells/Values touched: false/false'
// Transaction was rolled back.
store.callListener(listenerId);
// -> 'Cells/Values touched: false/false'
// It is meaningless to call this listener directly.
store
.delListener(listenerId)
.delListener(listenerId2)
.delListener(listenerId3);
Since
v1.3.0
addStartTransactionListener
The addStartTransactionListener method registers a listener function with the Store that will be called at the start of a transaction.
addStartTransactionListener(listener: TransactionListener<MergeableStore>): string| Type | Description | |
|---|---|---|
listener | TransactionListener<MergeableStore> | The function that will be called at the start of a transaction. |
| returns | string | A unique |
The provided TransactionListener will receive a reference to the Store and two booleans to indicate whether Cell or Value data has been touched during the transaction. Since this is called at the start, they will both be false!
Note that a TransactionListener added to the Store with this method can mutate the Store, and its changes will be treated as part of the transaction that is starting.
Example
This example registers a listener that is called at start end of the transaction, just before its listeners will be called.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
})
.setValues({open: true, employees: 3});
const listenerId = store.addStartTransactionListener(() => {
console.log('Transaction started');
});
store.transaction(() =>
store.setCell('pets', 'fido', 'color', 'brown').setValue('employees', 3),
);
// -> 'Transaction started'
store.callListener(listenerId);
// -> 'Transaction started'
store.delListener(listenerId);
Since
v3.2.0
addWillFinishTransactionListener
The addWillFinishTransactionListener method registers a listener function with the Store that will be called just before other non-mutating listeners are called at the end of the transaction.
addWillFinishTransactionListener(listener: TransactionListener<MergeableStore>): string| Type | Description | |
|---|---|---|
listener | TransactionListener<MergeableStore> | The function that will be called before the end of a transaction. |
| returns | string | A unique |
This is useful if you need to know that a set of listeners are about to be called at the end of a transaction, perhaps to batch their consequences together.
The provided TransactionListener will receive a reference to the Store and two booleans to indicate whether Cell or Value data has been touched during the transaction. The two flags are intended as a hint about whether non-mutating listeners might be being called at the end of the transaction.
Here, 'touched' means that Cell or Value data has either been changed, or changed and then changed back to its original value during the transaction. The exception is a transaction that has been rolled back, for which the value of cellsTouched and valuesTouched in the listener will be false because all changes have been reverted.
Note that a TransactionListener added to the Store with this method can mutate the Store itself, and its changes will be treated as part of the transaction that is starting (and may fire non-mutating listeners after this).
Example
This example registers a listener that is called at the end of the transaction, just before its listeners will be called. The transactions shown here variously change, touch, and rollback cells, demonstrating how the cellsTouched and valuesTouched parameters in the listener work.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
})
.setValues({open: true, employees: 3});
const listenerId = store.addWillFinishTransactionListener((store) => {
const [cellsTouched, valuesTouched] = store.getTransactionLog() ?? {};
console.log(`Cells/Values touched: ${cellsTouched}/${valuesTouched}`);
});
const listenerId2 = store.addTablesListener(() =>
console.log('Tables changed'),
);
const listenerId3 = store.addValuesListener(() =>
console.log('Values changed'),
);
store.transaction(() =>
store.setCell('pets', 'fido', 'color', 'brown').setValue('employees', 3),
);
// -> 'Cells/Values touched: false/false'
store.transaction(() => store.setCell('pets', 'fido', 'color', 'walnut'));
// -> 'Cells/Values touched: true/false'
// -> 'Tables changed'
store.transaction(() => store.setValue('employees', 4));
// -> 'Cells/Values touched: false/true'
// -> 'Values changed'
store.transaction(() => {
store
.setRow('pets', 'felix', {species: 'cat'})
.delRow('pets', 'felix')
.setValue('city', 'London')
.delValue('city');
});
// -> 'Cells/Values touched: true/true'
// But no Tables or Values listeners fired since there are no net changes.
store.transaction(
() =>
store
.setRow('pets', 'felix', {species: 'cat'})
.setValue('city', 'London'),
() => true,
);
// -> 'Cells/Values touched: false/false'
// Transaction was rolled back.
store.callListener(listenerId);
// -> 'Cells/Values touched: false/false'
// It is meaningless to call this listener directly.
store
.delListener(listenerId)
.delListener(listenerId2)
.delListener(listenerId3);
Since
v1.3.0
callListener
The callListener method provides a way for you to manually provoke a listener to be called, even if the underlying data hasn't changed.
callListener(listenerId: string): thisThis is useful when you are using mutator listeners to guarantee that data conforms to programmatic conditions, and those conditions change such that you need to update the Store in bulk.
Examples
This example registers a listener that ensures a Cell has one of list of a valid values. After that list changes, the listener is called to apply the condition to the existing data.
import {createStore} from 'tinybase';
const validColors = ['walnut', 'brown', 'black'];
const store = createStore();
const listenerId = store.addCellListener(
'pets',
null,
'color',
(store, tableId, rowId, cellId, color) => {
if (!validColors.includes(color)) {
store.setCell(tableId, rowId, cellId, validColors[0]);
}
},
true,
);
store.setRow('pets', 'fido', {species: 'dog', color: 'honey'});
console.log(store.getRow('pets', 'fido'));
// -> {species: 'dog', color: 'walnut'}
validColors.shift();
console.log(validColors);
// -> ['brown', 'black']
store.callListener(listenerId);
console.log(store.getRow('pets', 'fido'));
// -> {species: 'dog', color: 'brown'}
store.delListener(listenerId);
This example registers a listener to Row Id changes. It is explicitly called and fires for two Tables in the Store.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
const listenerId = store.addRowIdsListener(null, (store, tableId) => {
console.log(`Row Ids listener called for ${tableId} table`);
});
store.callListener(listenerId);
// -> 'Row Ids listener called for pets table'
// -> 'Row Ids listener called for species table'
store.delListener(listenerId);
This example registers a listener Value changes. It is explicitly called and fires for two Values in the Store.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addValueListener(
null,
(store, valueId, value) => {
console.log(`Value listener called for ${valueId} value, ${value}`);
},
);
store.callListener(listenerId);
// -> 'Value listener called for open value, true'
// -> 'Value listener called for employees value, 3'
store.delListener(listenerId);
This example registers listeners for the end of transactions, and for invalid Cells. They are explicitly called, meaninglessly. The former receives empty arguments. The latter is not called at all.
import {createStore} from 'tinybase';
const store = createStore();
const listenerId = store.addWillFinishTransactionListener(
(store, cellsTouched, valuesTouched) => {
console.log(`Transaction finish: ${cellsTouched}/${valuesTouched}`);
},
);
store.callListener(listenerId);
// -> 'Transaction finish: undefined/undefined'
store.delListener(listenerId);
const listenerId2 = store.addInvalidCellListener(
null,
null,
null,
(store, tableId, rowId, cellId) => {
console.log('Invalid cell', tableId, rowId, cellId);
},
);
store.callListener(listenerId2);
// -> undefined
store.delListener(listenerId2);
Since
v1.0.0
delListener
The delListener method removes a listener that was previously added to the Store.
delListener(listenerId: string): this| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | this | A reference to the |
Use the Id returned by whichever method was used to add the listener. Note that the Store may re-use this Id for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTablesListener(() => {
console.log('Tables changed');
});
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Tables changed'
store.delListener(listenerId);
store.setCell('pets', 'fido', 'color', 'honey');
// -> undefined
// The listener is not called.
Since
v1.0.0
Iterator methods
forEachTable
The forEachTable method takes a function that it will then call for each Table in the Store.
forEachTable(tableCallback: TableCallback): void| Type | Description | |
|---|---|---|
tableCallback | TableCallback | The function that should be called for every |
| returns | void | This has no return value. |
This method is useful for iterating over the Table structure of the Store in a functional style. The tableCallback parameter is a TableCallback function that will be called with the Id of each Table, and with a function that can then be used to iterate over each Row of the Table, should you wish.
Example
This example iterates over each Table in a Store, and lists each Row Id within them.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
store.forEachTable((tableId, forEachRow) => {
console.log(tableId);
forEachRow((rowId) => console.log(`- ${rowId}`));
});
// -> 'pets'
// -> '- fido'
// -> 'species'
// -> '- dog'
Since
v1.0.0
forEachTableCell
The forEachTableCell method takes a function that it will then call for each Cell used across the whole Table.
forEachTableCell(
tableId: string,
tableCellCallback: TableCellCallback,
): void| Type | Description | |
|---|---|---|
tableId | string | |
tableCellCallback | TableCellCallback | The function that should be called for every |
| returns | void | This has no return value. |
This method is useful for iterating over the Cell structure of the Table in a functional style. The tableCellCallback parameter is a TableCellCallback function that will be called with the Id of each Cell and the count of Rows in the Table in which it appears.
Example
This example iterates over each Cell Id used across the whole Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}, felix: {species: 'cat', legs: 4}},
});
store.forEachTableCell('pets', (cellId, count) => {
console.log(`${cellId}: ${count}`);
});
// -> 'species: 2'
// -> 'legs: 1'
Since
v3.3.0
forEachRow
The forEachRow method takes a function that it will then call for each Row in a specified Table.
forEachRow(
tableId: string,
rowCallback: RowCallback,
): void| Type | Description | |
|---|---|---|
tableId | string | |
rowCallback | RowCallback | The function that should be called for every |
| returns | void | This has no return value. |
This method is useful for iterating over the Row structure of the Table in a functional style. The rowCallback parameter is a RowCallback function that will be called with the Id of each Row, and with a function that can then be used to iterate over each Cell of the Row, should you wish.
Example
This example iterates over each Row in a Table, and lists each Cell Id within them.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {color: 'black'},
},
});
store.forEachRow('pets', (rowId, forEachCell) => {
console.log(rowId);
forEachCell((cellId) => console.log(`- ${cellId}`));
});
// -> 'fido'
// -> '- species'
// -> 'felix'
// -> '- color'
Since
v1.0.0
forEachCell
The forEachCell method takes a function that it will then call for each Cell in a specified Row.
forEachCell(
tableId: string,
rowId: string,
cellCallback: CellCallback,
): void| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
cellCallback | CellCallback | The function that should be called for every |
| returns | void | This has no return value. |
This method is useful for iterating over the Cell structure of the Row in a functional style. The cellCallback parameter is a CellCallback function that will be called with the Id and value of each Cell.
Example
This example iterates over each Cell in a Row, and lists its value.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
store.forEachCell('pets', 'fido', (cellId, cell) => {
console.log(`${cellId}: ${cell}`);
});
// -> 'species: dog'
// -> 'color: brown'
Since
v1.0.0
forEachValue
The forEachValue method takes a function that it will then call for each Value in a Store.
forEachValue(valueCallback: ValueCallback): void| Type | Description | |
|---|---|---|
valueCallback | ValueCallback | The function that should be called for every |
| returns | void | This has no return value. |
This method is useful for iterating over the Value structure of the Store in a functional style. The valueCallback parameter is a ValueCallback function that will be called with the Id and value of each Value.
Example
This example iterates over each Value in a Store, and lists its value.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
store.forEachValue((valueId, value) => {
console.log(`${valueId}: ${value}`);
});
// -> 'open: true'
// -> 'employees: 3'
Since
v3.0.0
Syncing methods
getMergeableTableDiff
The getMergeableTableDiff method returns information about new and differing Table objects of a MergeableStore relative to another.
getMergeableTableDiff(otherTableHashes: TableHashes): [newTables: [thing: {[tableId: Id]: TableStamp<Hashed>}, hlc?: string], differingTableHashes: TableHashes]| Type | Description | |
|---|---|---|
otherTableHashes | TableHashes | The |
| returns | [newTables: [thing: {[tableId: Id]: TableStamp<Hashed>}, hlc?: string], differingTableHashes: TableHashes] | A pair of objects describing the new and differing |
The method is generally intended to be used internally within TinyBase itself and the return type is assumed to be opaque to applications that use it.
Example
This example creates two MergeableStores, sets some differing data, and then identifies the differences in the Table objects of one versus the other. Once they have been merged, the differences are empty.
import {createMergeableStore} from 'tinybase';
const store1 = createMergeableStore('store1');
store1.setTables({pets: {fido: {color: 'brown'}}});
const store2 = createMergeableStore('store2');
store2.setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
console.log(
store2.getMergeableTableDiff(store1.getMergeableTableHashes()),
);
// ->
[
[{species: [{dog: [{price: [5, 'Nn1JUF----0CnH-J']}]}]}],
{pets: 1212600658},
];
store1.merge(store2);
console.log(
store2.getMergeableTableDiff(store1.getMergeableTableHashes()),
);
// -> [[{}], {}]
Since
v5.0.0
getMergeableTableHashes
The getMergeableTableHashes method returns hashes for the Table objects in a MergeableStore.
getMergeableTableHashes(): TableHashes| returns | TableHashes | A |
|---|
If two Table Ids have different hashes, that indicates that the content within them is different and should be synchronized.
The method is generally intended to be used internally within TinyBase itself and the return type is assumed to be opaque to applications that use it.
Example
This example creates a MergeableStore, sets some data, and then accesses the Table hashes.
import {createMergeableStore} from 'tinybase';
const store = createMergeableStore('store1');
store.setCell('pets', 'fido', 'color', 'brown');
console.log(store.getMergeableTableHashes());
// -> {pets: 518810247}
store.setCell('species', 'dog', 'price', 5);
console.log(store.getMergeableTableHashes());
// -> {pets: 518810247, species: 2324343240}
Since
v5.0.0
getMergeableRowDiff
The getMergeableRowDiff method returns information about new and differing Row objects of a MergeableStore relative to another.
getMergeableRowDiff(otherTableRowHashes: RowHashes): [newRows: [thing: {[tableId: Id]: TableStamp<Hashed>}, hlc?: string], differingRowHashes: RowHashes]| Type | Description | |
|---|---|---|
otherTableRowHashes | RowHashes | The |
| returns | [newRows: [thing: {[tableId: Id]: TableStamp<Hashed>}, hlc?: string], differingRowHashes: RowHashes] | A pair of objects describing the new and differing |
The method is generally intended to be used internally within TinyBase itself and the return type is assumed to be opaque to applications that use it.
Example
This example creates two MergeableStores, sets some differing data, and then identifies the differences in the Row objects of one versus the other. Once they have been merged, the differences are empty.
import {createMergeableStore} from 'tinybase';
const store1 = createMergeableStore('store1');
store1.setTables({pets: {fido: {color: 'brown'}}});
const store2 = createMergeableStore('store2');
store2.setTables({pets: {fido: {color: 'black'}, felix: {color: 'tan'}}});
console.log(
store2.getMergeableRowDiff(
store1.getMergeableRowHashes(
store2.getMergeableTableDiff(store1.getMergeableTableHashes())[1],
),
),
);
// ->
[
[{pets: [{felix: [{color: ['tan', 'Nn1JUF----0CnH-J']}]}]}],
{pets: {fido: 1038491054}},
];
store1.merge(store2);
console.log(
store2.getMergeableRowDiff(
store1.getMergeableRowHashes(
store2.getMergeableTableDiff(store1.getMergeableTableHashes())[1],
),
),
);
// -> [[{}], {}]
Since
v5.0.0
getMergeableRowHashes
The getMergeableRowHashes method returns hashes for Row objects in a MergeableStore.
getMergeableRowHashes(otherTableHashes: TableHashes): RowHashes| Type | Description | |
|---|---|---|
otherTableHashes | TableHashes | The |
| returns | RowHashes | A |
If two Row Ids have different hashes, that indicates that the content within them is different and should be synchronized.
The method is generally intended to be used internally within TinyBase itself and the return type is assumed to be opaque to applications that use it.
Example
This example creates a MergeableStore, sets some data, and then accesses the Row hashes for the differing Table Ids.
import {createMergeableStore} from 'tinybase';
const store1 = createMergeableStore('store1');
store1.setTables({pets: {fido: {color: 'brown'}, felix: {color: 'tan'}}});
const store2 = createMergeableStore('store2');
store2.setTables({pets: {fido: {color: 'black'}, felix: {color: 'tan'}}});
console.log(
store1.getMergeableRowHashes(
store2.getMergeableTableDiff(store1.getMergeableTableHashes())[1],
),
);
// -> {pets: {felix: 1683761402, fido: 851131566}}
Since
v5.0.0
getMergeableCellDiff
The getMergeableCellDiff method returns information about new and differing Cell objects of a MergeableStore relative to another.
getMergeableCellDiff(otherTableRowCellHashes: CellHashes): [thing: {[tableId: Id]: TableStamp<Hashed>}, hlc?: string]| Type | Description | |
|---|---|---|
otherTableRowCellHashes | CellHashes | The |
| returns | [thing: {[tableId: Id]: TableStamp<Hashed>}, hlc?: string] | The new and differing |
The method is generally intended to be used internally within TinyBase itself and the return type is assumed to be opaque to applications that use it.
Example
This example creates two MergeableStores, sets some differing data, and then identifies the differences in the Cell objects of one versus the other. Once they have been merged, the differences are empty.
import {createMergeableStore} from 'tinybase';
const store1 = createMergeableStore('store1');
store1.setTables({pets: {fido: {color: 'brown'}}});
const store2 = createMergeableStore('store2');
store2.setTables({pets: {fido: {color: 'black', species: 'dog'}}});
console.log(
store2.getMergeableCellDiff(
store1.getMergeableCellHashes(
store2.getMergeableRowDiff(
store1.getMergeableRowHashes(
store2.getMergeableTableDiff(
store1.getMergeableTableHashes(),
)[1],
),
)[1],
),
),
);
// ->
[
{
pets: [
{
fido: [
{
color: ['black', 'Nn1JUF-----CnH-J'],
species: ['dog', 'Nn1JUF----0CnH-J'],
},
],
},
],
},
];
store1.merge(store2);
console.log(
store2.getMergeableCellDiff(
store1.getMergeableCellHashes(
store2.getMergeableRowDiff(
store1.getMergeableRowHashes(
store2.getMergeableTableDiff(
store1.getMergeableTableHashes(),
)[1],
),
)[1],
),
),
);
// -> [{}]
Since
v5.0.0
getMergeableCellHashes
The getMergeableCellHashes method returns hashes for Cell objects in a MergeableStore.
getMergeableCellHashes(otherTableRowHashes: RowHashes): CellHashes| Type | Description | |
|---|---|---|
otherTableRowHashes | RowHashes | The |
| returns | CellHashes | A |
If two Cell Ids have different hashes, that indicates that the content within them is different and should be synchronized.
The method is generally intended to be used internally within TinyBase itself and the return type is assumed to be opaque to applications that use it.
Example
This example creates a MergeableStore, sets some data, and then accesses the Cell hashes for the differing Table Ids.
import {createMergeableStore} from 'tinybase';
const store1 = createMergeableStore('store1');
store1.setTables({pets: {fido: {color: 'brown', species: 'dog'}}});
const store2 = createMergeableStore('store2');
store2.setTables({pets: {fido: {color: 'black', species: 'dog'}}});
console.log(
store1.getMergeableCellHashes(
store2.getMergeableRowDiff(
store1.getMergeableRowHashes(
store2.getMergeableTableDiff(store1.getMergeableTableHashes())[1],
),
)[1],
),
);
// -> {pets: {fido: {color: 923684530, species: 227729753}}}
Since
v5.0.0
getMergeableValueDiff
The getMergeableValueDiff method returns information about new and differing Value objects of a MergeableStore relative to another.
getMergeableValueDiff(otherValueHashes: ValueHashes): [thing: {[valueId: Id]: ValueStamp<Hashed>}, hlc?: string]| Type | Description | |
|---|---|---|
otherValueHashes | ValueHashes | The |
| returns | [thing: {[valueId: Id]: ValueStamp<Hashed>}, hlc?: string] | The new and differing |
The method is generally intended to be used internally within TinyBase itself and the return type is assumed to be opaque to applications that use it.
Example
This example creates two MergeableStores, sets some differing data, and then identifies the differences in the Value objects of one versus the other. Once they have been merged, the differences are empty.
import {createMergeableStore} from 'tinybase';
const store1 = createMergeableStore('store1');
store1.setValues({employees: 3});
const store2 = createMergeableStore('store2');
store2.setValues({employees: 4, open: true});
console.log(
store2.getMergeableValueDiff(store1.getMergeableValueHashes()),
);
// ->
[
{
employees: [4, 'Nn1JUF-----CnH-J'],
open: [true, 'Nn1JUF----0CnH-J'],
},
];
store1.merge(store2);
console.log(
store2.getMergeableValueDiff(store1.getMergeableValueHashes()),
);
// -> [{}]
Since
v5.0.0
getMergeableValueHashes
The getMergeableValueHashes method returns hashes for the Value objects in a MergeableStore.
getMergeableValueHashes(): ValueHashes| returns | ValueHashes | A |
|---|
If two Value Ids have different hashes, that indicates that the content within them is different and should be synchronized.
The method is generally intended to be used internally within TinyBase itself and the return type is assumed to be opaque to applications that use it.
Example
This example creates a MergeableStore, sets some data, and then accesses the Value hashes.
import {createMergeableStore} from 'tinybase';
const store = createMergeableStore('store1');
store.setValue('employees', 3);
console.log(store.getMergeableValueHashes());
// -> {employees: 1940815977}
store.setValue('open', true);
console.log(store.getMergeableValueHashes());
// -> {employees: 1940815977, open: 3860530645}
Since
v5.0.0
getMergeableContentHashes
The getMergeableContentHashes method returns hashes for the full content of a MergeableStore.
getMergeableContentHashes(): ContentHashes| returns | ContentHashes | A |
|---|
If two MergeableStore instances have different hashes, that indicates that the mergeable Tables or Values within them are different and should be synchronized.
The method is generally intended to be used internally within TinyBase itself and the return type is assumed to be opaque to applications that use it.
Example
This example creates a MergeableStore, sets some data, and then accesses the content hashes.
import {createMergeableStore} from 'tinybase';
const store = createMergeableStore('store1');
store.setCell('pets', 'fido', 'color', 'brown');
console.log(store.getMergeableContentHashes());
// -> [784336119, 0]
store.setValue('open', true);
console.log(store.getMergeableContentHashes());
// -> [784336119, 2829789038]
Since
v5.0.0
Transaction methods
finishTransaction
The finishTransaction method allows you to explicitly finish a transaction that has made multiple mutations to the Store, triggering all calls to the relevant listeners.
finishTransaction(doRollback?: DoRollback): this| Type | Description | |
|---|---|---|
doRollback? | DoRollback | An optional callback that should return |
| returns | this | A reference to the |
Transactions are useful for making bulk changes to the data in a Store, and when you don't want listeners to be called as you make each change. Changes are made silently during the transaction, and listeners relevant to the changes you have made will instead only be called when the whole transaction is complete.
Generally it is preferable to use the transaction method to wrap a block of code as a transaction. It simply calls both the startTransaction and finishTransaction methods for you. See that method for several transaction examples.
Use this finishTransaction method when you have a more 'open-ended' transaction, such as one containing mutations triggered from other events that are asynchronous or not occurring inline to your code. There must have been a corresponding startTransaction method that this completes, of course, otherwise this function has no effect.
The optional parameter, doRollback is a DoRollback callback that you can use to rollback the transaction if it did not complete to your satisfaction. It is called with getTransactionChanges and getTransactionLog parameters, which inform you of the net changes that have been made during the transaction, at different levels of detail. See the DoRollback documentation for more details.
Examples
This example makes changes to two Cells, first outside, and secondly within, a transaction that is explicitly started and finished. In the second case, the Row listener is only called once.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.addRowListener('pets', 'fido', () => console.log('Fido changed'));
store
.setCell('pets', 'fido', 'color', 'brown')
.setCell('pets', 'fido', 'sold', false);
// -> 'Fido changed'
// -> 'Fido changed'
store
.startTransaction()
.setCell('pets', 'fido', 'color', 'walnut')
.setCell('pets', 'fido', 'sold', true)
.finishTransaction();
// -> 'Fido changed'
This example makes multiple changes to the Store, including some attempts to update a Cell with invalid values. The doRollback callback receives information about the changes and invalid attempts, and then judges that the transaction should be rolled back to its original state.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({pets: {fido: {species: 'dog', color: 'brown'}}})
.setValues({open: true});
store
.startTransaction()
.setCell('pets', 'fido', 'color', 'black')
.setCell('pets', 'fido', 'eyes', ['left', 'right'])
.setCell('pets', 'fido', 'info', {sold: null})
.setValue('open', false)
.setValue('employees', ['alice', 'bob'])
.finishTransaction(() => {
const [, , changedCells, invalidCells, changedValues, invalidValues] =
store.getTransactionLog();
console.log(store.getTables());
console.log(changedCells);
console.log(invalidCells);
console.log(changedValues);
console.log(invalidValues);
return invalidCells['pets'] != null;
});
// -> {pets: {fido: {species: 'dog', color: 'black'}}}
// -> {pets: {fido: {color: ['brown', 'black']}}}
// -> {pets: {fido: {eyes: [['left', 'right']], info: [{sold: null}]}}}
// -> {open: [true, false]}
// -> {employees: [['alice', 'bob']]}
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'brown'}}}
console.log(store.getValues());
// -> {open: true}
Since
v1.3.0
getTransactionChanges
The getTransactionChanges method returns the net meaningful changes that have been made to a Store during a transaction.
getTransactionChanges(): ChangesThis is useful for deciding whether to rollback a transaction, for example. The returned object is only meaningful if the method is called when the Store is in a transaction - such as in a TransactionListener.
Example
This example makes changes to the Store. At the end of the transaction, detail about what changed is enumerated.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({pets: {fido: {species: 'dog', color: 'brown'}}})
.setValues({open: true});
store
.startTransaction()
.setCell('pets', 'fido', 'color', 'black')
.setValue('open', false)
.finishTransaction(() => {
const [changedCells, changedValues] = store.getTransactionChanges();
console.log(changedCells);
console.log(changedValues);
});
// -> {pets: {fido: {color: 'black'}}}
// -> {open: false}
Since
v5.0.0
getTransactionLog
The getTransactionLog method returns the changes that were made to a Store during a transaction in more detail, including invalid changes, and what previous values were.
getTransactionLog(): TransactionLog| returns | TransactionLog | A |
|---|
This is useful for deciding whether to rollback a transaction, for example. The returned object is only meaningful if the method is called when the Store is in a transaction - such as in a TransactionListener.
Example
This example makes changes to the Store. At the end of the transaction, detail about what changed is enumerated.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({pets: {fido: {species: 'dog', color: 'brown'}}})
.setValues({open: true});
store
.startTransaction()
.setCell('pets', 'fido', 'color', 'black')
.setCell('pets', 'fido', 'eyes', ['left', 'right'])
.setCell('pets', 'fido', 'info', {sold: null})
.setValue('open', false)
.setValue('employees', ['alice', 'bob'])
.finishTransaction(() => {
const [, , changedCells, invalidCells, changedValues, invalidValues] =
store.getTransactionLog();
console.log(changedCells);
console.log(invalidCells);
console.log(changedValues);
console.log(invalidValues);
});
// -> {pets: {fido: {color: ['brown', 'black']}}}
// -> {pets: {fido: {eyes: [['left', 'right']], info: [{sold: null}]}}}
// -> {open: [true, false]}
// -> {employees: [['alice', 'bob']]}
Since
v5.0.0
getTransactionMergeableChanges
The getTransactionMergeableChanges method returns the net meaningful changes that have been made to a MergeableStore during a transaction.
getTransactionMergeableChanges(withHashes?: boolean): MergeableChanges<true>| Type | Description | |
|---|---|---|
withHashes? | boolean | Whether to include hashes in the output, defaulting to false. |
| returns | MergeableChanges<true> | A |
The method is generally intended to be used internally within TinyBase itself and the return type is assumed to be opaque to applications that use it.
Example
This example makes changes to the MergeableStore. At the end of the transaction, detail about what changed is enumerated.
import {createMergeableStore} from 'tinybase';
const store = createMergeableStore('store1');
store.setTables({pets: {fido: {species: 'dog', color: 'brown'}}});
store.setValues({open: true});
store
.startTransaction()
.setCell('pets', 'fido', 'color', 'black')
.setValue('open', false)
.finishTransaction(() => {
console.log(store.getTransactionMergeableChanges());
});
// ->
[
[{pets: [{fido: [{color: ['black', 'Nn1JUF----2FnHIC']}]}]}],
[{open: [false, 'Nn1JUF----3FnHIC']}],
1,
];
Since
v5.0.0
startTransaction
The startTransaction method allows you to explicitly start a transaction that will make multiple mutations to the Store, buffering all calls to the relevant listeners until it completes when you call the finishTransaction method.
startTransaction(): this| returns | this | A reference to the |
|---|
Transactions are useful for making bulk changes to the data in a Store, and when you don't want listeners to be called as you make each change. Changes are made silently during the transaction, and listeners relevant to the changes you have made will instead only be called when the whole transaction is complete.
Generally it is preferable to use the transaction method to wrap a block of code as a transaction. It simply calls both the startTransaction and finishTransaction methods for you. See that method for several transaction examples.
Use this startTransaction method when you have a more 'open-ended' transaction, such as one containing mutations triggered from other events that are asynchronous or not occurring inline to your code. You must remember to also call the finishTransaction method explicitly when it is done, of course.
Example
This example makes changes to two Cells, first outside, and secondly within, a transaction that is explicitly started and finished. In the second case, the Row listener is only called once.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.addRowListener('pets', 'fido', () => console.log('Fido changed'));
store
.setCell('pets', 'fido', 'color', 'brown')
.setCell('pets', 'fido', 'sold', false);
// -> 'Fido changed'
// -> 'Fido changed'
store
.startTransaction()
.setCell('pets', 'fido', 'color', 'walnut')
.setCell('pets', 'fido', 'sold', true)
.finishTransaction();
// -> 'Fido changed'
Since
v1.3.0
transaction
The transaction method takes a function that makes multiple mutations to the Store, buffering all calls to the relevant listeners until it completes.
transaction<Return>(
actions: () => Return,
doRollback?: DoRollback,
): Return| Type | Description | |
|---|---|---|
actions | () => Return | The function to be executed as a transaction. |
doRollback? | DoRollback | An optional callback that should return |
| returns | Return | Whatever value the provided transaction function returns. |
This method is useful for making bulk changes to the data in a Store, and when you don't want listeners to be called as you make each change. Changes are made silently during the transaction, and listeners relevant to the changes you have made will instead only be called when the whole transaction is complete.
If multiple changes are made to a piece of Store data throughout the transaction, a relevant listener will only be called with the final value (assuming it is different to the value at the start of the transaction), regardless of the changes that happened in between. For example, if a Cell had a value 'a' and then, within a transaction, it was changed to 'b' and then 'c', any CellListener registered for that cell would be called once as if there had been a single change from 'a' to 'c'.
Transactions can be nested. Relevant listeners will be called only when the outermost one completes.
The second, optional parameter, doRollback is a DoRollback callback that you can use to rollback the transaction if it did not complete to your satisfaction. See the DoRollback documentation for more details.
Examples
This example makes changes to two Cells, first outside, and secondly within, a transaction. In the second case, the Row listener is only called once.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.addRowListener('pets', 'fido', () => console.log('Fido changed'));
store
.setCell('pets', 'fido', 'color', 'brown')
.setCell('pets', 'fido', 'sold', false);
// -> 'Fido changed'
// -> 'Fido changed'
store.transaction(() =>
store
.setCell('pets', 'fido', 'color', 'walnut')
.setCell('pets', 'fido', 'sold', true),
);
// -> 'Fido changed'
This example makes multiple changes to one Cell. The Cell listener is called once - and with the final value - only if there is a net overall change.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.addCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId, newCell) => console.log(newCell),
);
store.transaction(() =>
store
.setCell('pets', 'fido', 'color', 'black')
.setCell('pets', 'fido', 'color', 'brown')
.setCell('pets', 'fido', 'color', 'walnut'),
);
// -> 'walnut'
store.transaction(() =>
store
.setCell('pets', 'fido', 'color', 'black')
.setCell('pets', 'fido', 'color', 'walnut'),
);
// -> undefined
// No net change during the transaction, so the listener is not called.
This example makes multiple changes to the Store, including some attempts to update a Cell and Value with invalid values. The doRollback callback receives information about the changes and invalid attempts, and then judges that the transaction should be rolled back to its original state.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({pets: {fido: {species: 'dog', color: 'brown'}}})
.setValues({open: true});
store.transaction(
() =>
store
.setCell('pets', 'fido', 'color', 'black')
.setCell('pets', 'fido', 'eyes', ['left', 'right'])
.setCell('pets', 'fido', 'info', {sold: null})
.setValue('open', false)
.setValue('employees', ['alice', 'bob']),
() => {
const [, , changedCells, invalidCells, changedValues, invalidValues] =
store.getTransactionLog();
console.log(store.getTables());
console.log(changedCells);
console.log(invalidCells);
console.log(changedValues);
console.log(invalidValues);
return invalidCells['pets'] != null;
},
);
// -> {pets: {fido: {species: 'dog', color: 'black'}}}
// -> {pets: {fido: {color: ['brown', 'black']}}}
// -> {pets: {fido: {eyes: [['left', 'right']], info: [{sold: null}]}}}
// -> {open: [true, false]}
// -> {employees: [['alice', 'bob']]}
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'brown'}}}
console.log(store.getValues());
// -> {open: true}
Since
v1.0.0
Deleter methods
delTables
The delTables method lets you remove all of the data in a Store.
delTables(): this| returns | this | A reference to the |
|---|
Example
This example removes the data of a Store.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.delTables();
console.log(store.getTables());
// -> {}
Since
v1.0.0
delTablesSchema
The delTablesSchema method lets you remove the TablesSchema of the Store.
delTablesSchema(): this| returns | this | A reference to the |
|---|
Example
This example removes the TablesSchema of a Store.
import {createStore} from 'tinybase';
const store = createStore().setTablesSchema({
pets: {species: {type: 'string'}},
});
store.delTablesSchema();
console.log(store.getTablesSchemaJson());
// -> '{}'
Since
v1.0.0
delTable
The delTable method lets you remove a single Table from the Store.
delTable(tableId: string): thisExample
This example removes a Table from a Store.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
store.delTable('pets');
console.log(store.getTables());
// -> {species: {dog: {price: 5}}}
Since
v1.0.0
delRow
The delRow method lets you remove a single Row from a Table.
delRow(
tableId: string,
rowId: string,
): this| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
| returns | this | A reference to the |
If this is the last Row in its Table, then that Table will be removed.
Example
This example removes a Row from a Table.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}, felix: {species: 'cat'}},
});
store.delRow('pets', 'fido');
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
Since
v1.0.0
delCell
The delCell method lets you remove a single Cell from a Row.
delCell(
tableId: string,
rowId: string,
cellId: string,
forceDel?: boolean,
): this| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
forceDel? | boolean | An optional flag to indicate that the whole |
| returns | this | A reference to the |
When there is no TablesSchema applied to the Store, then if this is the last Cell in its Row, then that Row will be removed. If, in turn, that is the last Row in its Table, then that Table will be removed.
If there is a TablesSchema applied to the Store and it specifies a default value for this Cell, then deletion will result in it being set back to its default value. To override this, use the forceDel parameter, as described below.
The forceDel parameter is an optional flag that is only relevant if a TablesSchema provides a default value for this Cell. Under such circumstances, deleting a Cell value will normally restore it to the default value. If this flag is set to true, the complete removal of the Cell is instead guaranteed. But since doing do so would result in an invalid Row (according to the TablesSchema), in fact the whole Row is deleted to retain the integrity of the Table. Therefore, this flag should be used with caution.
Examples
This example removes a Cell from a Row without a TablesSchema.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', sold: true}},
});
store.delCell('pets', 'fido', 'sold');
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
This example removes a Cell from a Row with a TablesSchema that defaults its value.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({
pets: {fido: {species: 'dog', sold: true}},
})
.setTablesSchema({
pets: {
species: {type: 'string'},
sold: {type: 'boolean', default: false},
},
});
store.delCell('pets', 'fido', 'sold');
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', sold: false}}}
This example removes a Cell from a Row with a TablesSchema that defaults its value, but uses the forceDel parameter to override it.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({
pets: {fido: {species: 'dog', sold: true}, felix: {species: 'cat'}},
})
.setTablesSchema({
pets: {
species: {type: 'string'},
sold: {type: 'boolean', default: false},
},
});
store.delCell('pets', 'fido', 'sold', true);
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat', sold: false}}}
Since
v1.0.0
delValues
The delValues method lets you remove all the Values from a Store.
delValues(): this| returns | this | A reference to the |
|---|
If there is a ValuesSchema applied to the Store and it specifies a default value for any Value Id, then deletion will result in it being set back to its default value.
Examples
This example removes all Values from a Store without a ValuesSchema.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
store.delValues();
console.log(store.getValues());
// -> {}
This example removes all Values from a Store with a ValuesSchema that defaults one of its values.
import {createStore} from 'tinybase';
const store = createStore()
.setValues({open: true, employees: 3})
.setValuesSchema({
open: {type: 'boolean', default: false},
employees: {type: 'number'},
});
store.delValues();
console.log(store.getValues());
// -> {open: false}
Since
v3.0.0
delValuesSchema
The delValuesSchema method lets you remove the ValuesSchema of the Store.
delValuesSchema(): this| returns | this | A reference to the |
|---|
Example
This example removes the ValuesSchema of a Store.
import {createStore} from 'tinybase';
const store = createStore().setValuesSchema({
sold: {type: 'boolean', default: false},
});
store.delValuesSchema();
console.log(store.getValuesSchemaJson());
// -> '{}'
Since
v3.0.0
delValue
The delValue method lets you remove a single Value from a Store.
delValue(valueId: string): thisIf there is a ValuesSchema applied to the Store and it specifies a default value for this Value Id, then deletion will result in it being set back to its default value.
Examples
This example removes a Value from a Store without a ValuesSchema.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
store.delValue('employees');
console.log(store.getValues());
// -> {open: true}
This example removes a Value from a Store with a ValuesSchema that defaults its value.
import {createStore} from 'tinybase';
const store = createStore()
.setValues({open: true, employees: 3})
.setValuesSchema({
open: {type: 'boolean', default: false},
employees: {type: 'number'},
});
store.delValue('open');
console.log(store.getValues());
// -> {open: false, employees: 3}
Since
v3.0.0
delSchema
The delSchema method lets you remove both the TablesSchema and ValuesSchema of the Store.
delSchema(): this| returns | this | A reference to the |
|---|
Prior to v3.0, this method removed the TablesSchema only.
Example
This example removes the TablesSchema and ValuesSchema of a Store.
import {createStore} from 'tinybase';
const store = createStore()
.setTablesSchema({
pets: {species: {type: 'string'}},
})
.setValuesSchema({
sold: {type: 'boolean', default: false},
});
store.delSchema();
console.log(store.getSchemaJson());
// -> '[{},{}]'
Since
v3.0.0
Development methods
getListenerStats
The getListenerStats method provides a set of statistics about the listeners registered with the Store, and is used for debugging purposes.
getListenerStats(): StoreListenerStats| returns | StoreListenerStats | A |
|---|
The StoreListenerStats object contains a breakdown of the different types of listener. Totals include both mutator and non-mutator listeners.
The method is intended to be used during development to ensure your application is not leaking listener registrations, for example.
Example
This example gets the listener statistics of a small and simple Store.
import {createStore} from 'tinybase';
const store = createStore();
store.addTablesListener(() => console.log('Tables changed'));
store.addRowIdsListener(() => console.log('Row Ids changed'));
const listenerStats = store.getListenerStats();
console.log(listenerStats.rowIds);
// -> 1
console.log(listenerStats.tables);
// -> 1
Since
v1.0.0
Functions
createMergeableStore
The createMergeableStore function creates a MergeableStore, and is the main entry point into the mergeable-store module.
createMergeableStore(
uniqueId?: string,
getNow?: GetNow,
): MergeableStore| Type | Description | |
|---|---|---|
uniqueId? | string | An optional unique |
getNow? | GetNow | An optional function that generates millisecond timestamps, since v6.1.0, defaulting to |
| returns | MergeableStore | A reference to the new |
There are two optional parameters which are only for testing and advanced usage.
The first is a uniqueId for the MergeableStore, used to distinguish conflicting changes made in the same millisecond by two different MergeableStore objects as its hash is added to the end of the HLC timestamps. Generally this can be omitted unless you have a need for deterministic HLCs, such as in a testing scenario. Otherwise, TinyBase will assign a unique Id to the Store at the time of creation.
Since v6.1.0, the second is a function that can be used to replace the way the timestamp is generated for HLCs (by default JavaScript's Date.now() method).
Examples
This example creates a MergeableStore.
import {createMergeableStore} from 'tinybase';
const store = createMergeableStore('store1');
console.log(store.getContent());
// -> [{}, {}]
console.log(store.getMergeableContent());
// -> [[{}, '', 0], [{}, '', 0]]
This example creates a MergeableStore with some initial data:
import {createMergeableStore} from 'tinybase';
const store = createMergeableStore('store1').setTables({
pets: {fido: {species: 'dog'}},
});
console.log(store.getContent());
// -> [{pets: {fido: {species: 'dog'}}}, {}]
console.log(store.getMergeableContent());
// ->
[
[
{
pets: [
{
fido: [
{species: ['dog', 'Nn1JUF-----FnHIC', 290599168]},
'',
2682656941,
],
},
'',
2102515304,
],
},
'',
3506229770,
],
[{}, '', 0],
];
This example creates a MergeableStore with some initial data and a TablesSchema:
import {createMergeableStore} from 'tinybase';
const store = createMergeableStore('store1')
.setTables({pets: {fido: {species: 'dog'}}})
.setTablesSchema({
pets: {
species: {type: 'string'},
sold: {type: 'boolean', default: false},
},
});
console.log(store.getContent());
// -> [{pets: {fido: {sold: false, species: 'dog'}}}, {}]
console.log(store.getMergeableContent());
// ->
[
[
{
pets: [
{
fido: [
{
sold: [false, 'Nn1JUF----2FnHIC', 2603026204],
species: ['dog', 'Nn1JUF----1FnHIC', 2817056260],
},
'',
2859424112,
],
},
'',
1640515891,
],
},
'',
2077041985,
],
[{}, '', 0],
];
Since
v5.0.0
Type Aliases
Mergeable type aliases
MergeableChanges
The MergeableChanges type represents changes to the content of a MergeableStore and the metadata about that content) required to merge it with another.
[mergeableTables: TablesStamp<Hashed>, mergeableValues: ValuesStamp<Hashed>, isChanges: 1]It is simply an array of two Stamp types, one for changes to the MergeableStore's Tables and one for changes to its Values. A final 1 is used to distinguish it from a full MergeableContent object.
This type is mostly utilized internally within TinyBase itself and is generally assumed to be opaque to applications that use it.
Since
v5.0.0
MergeableContent
The MergeableContent type represents the content of a MergeableStore and the metadata about that content) required to merge it with another.
[mergeableTables: TablesStamp<true>, mergeableValues: ValuesStamp<true>]It is simply an array of two Stamp types, one for the MergeableStore's Tables and one for its Values.
This type is mostly utilized internally within TinyBase itself and is generally assumed to be opaque to applications that use it.
Since
v5.0.0
Stamps type aliases
TablesStamp
The TablesStamp type is used as metadata to decide how to merge two different sets of Tables together.
Stamp<{[tableId: Id]: TableStamp<Hashed>}, Hashed>This type is mostly utilized internally within TinyBase itself and is generally assumed to be opaque to applications that use it.
Since
v5.0.0
TableStamp
The TableStamp type is used as metadata to decide how to merge two different Table objects together.
Stamp<{[rowId: Id]: RowStamp<Hashed>}, Hashed>This type is mostly utilized internally within TinyBase itself and is generally assumed to be opaque to applications that use it.
Since
v5.0.0
RowStamp
The RowStamp type is used as metadata to decide how to merge two different Row objects together.
Stamp<{[cellId: Id]: CellStamp<Hashed>}, Hashed>This type is mostly utilized internally within TinyBase itself and is generally assumed to be opaque to applications that use it.
Since
v5.0.0
CellStamp
The CellStamp type is used as metadata to decide how to merge two different Cell objects together.
Stamp<CellOrUndefined, Hashed>This type is mostly utilized internally within TinyBase itself and is generally assumed to be opaque to applications that use it.
Since
v5.0.0
ValuesStamp
The ValuesStamp type is used as metadata to decide how to merge two different sets of Values together.
Stamp<{[valueId: Id]: ValueStamp<Hashed>}, Hashed>This type is mostly utilized internally within TinyBase itself and is generally assumed to be opaque to applications that use it.
Since
v5.0.0
ValueStamp
The ValueStamp type is used as metadata to decide how to merge two different Value objects together.
Stamp<ValueOrUndefined, Hashed>This type is mostly utilized internally within TinyBase itself and is generally assumed to be opaque to applications that use it.
Since
v5.0.0
Stamp
The Stamp type is used as metadata to decide how to merge two different MergeableStore objects together.
Hashed extends true ? [thing: Thing, hlc: Hlc, hash: Hash] : [thing: Thing, hlc?: Hlc]It describes a combination of a value (or object), an Hlc timestamp, and optionally a Hash, all in an array.
This type is mostly utilized internally within TinyBase itself and is generally assumed to be opaque to applications that use it.
Since
v5.0.0
Syncing type aliases
TableHashes
The TableHashes type is used to quickly compare the content of two sets of Table objects.
{[tableId: Id]: Hash}It is simply an object of Hash types, one for each Table Id in the MergeableStore.
This type is mostly utilized internally within TinyBase itself and is generally assumed to be opaque to applications that use it.
Since
v5.0.0
RowHashes
The RowHashes type is used to quickly compare the content of two sets of Row objects.
{[tableId: Id]: {[rowId: Id]: Hash}}It is simply a nested object of Hash types, one for each Row Id, for each TableId, in the MergeableStore.
This type is mostly utilized internally within TinyBase itself and is generally assumed to be opaque to applications that use it.
Since
v5.0.0
CellHashes
The CellHashes type is used to quickly compare the content of two sets of Cell objects.
{[tableId: Id]: {[rowId: Id]: {[cellId: Id]: Hash}}}It is simply a nested object of Hash types, one for each Cell Id, for each Row Id, for each TableId, in the MergeableStore.
This type is mostly utilized internally within TinyBase itself and is generally assumed to be opaque to applications that use it.
Since
v5.0.0
ValueHashes
The ValueHashes type is used to quickly compare the content of two sets of Value objects.
{[valueId: Id]: Hash}It is simply an object of Hash types, one for each Value Id in the MergeableStore.
This type is mostly utilized internally within TinyBase itself and is generally assumed to be opaque to applications that use it.
Since
v5.0.0
ContentHashes
The ContentHashes type is used to quickly compare the content of two MergeableStore objects.
[tablesHash: Hash, valuesHash: Hash]It is simply an array of two Hash types, one for the MergeableStore's Tables and one for its Values.
This type is mostly utilized internally within TinyBase itself and is generally assumed to be opaque to applications that use it.
Since
v5.0.0
metrics
The metrics module of the TinyBase project provides the ability to create and track metrics and aggregates of the data in Store objects.
The main entry point to this module is the createMetrics function, which returns a new Metrics object. From there, you can create new Metric definitions, access the values of those Metrics directly, and register listeners for when they change.
Since
v1.0.0
Interfaces
Metrics
A Metrics object lets you define, query, and listen to, aggregations of Cell values within a Table in a Store.
This is useful for counting the number of Row objects in a Table, averaging Cell values, or efficiently performing any arbitrary aggregations.
Create a Metrics object easily with the createMetrics function. From there, you can add new Metric definitions (with the setMetricDefinition method), query their values (with the getMetric method), and add listeners for when they change (with the addMetricListener method).
This module provides a number of predefined and self-explanatory aggregations ('sum', 'avg', 'min', and 'max'), and defaults to counting Row objects when using the setMetricDefinition method. However, far more complex aggregations can be configured with custom functions.
Example
This example shows a very simple lifecycle of a Metrics object: from creation, to adding a definition, getting a Metric, and then registering and removing a listener for it.
import {createMetrics, createStore} from 'tinybase';
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition(
'highestPrice', // metricId
'species', // tableId to aggregate
'max', // aggregation
'price', // cellId to aggregate
);
console.log(metrics.getMetric('highestPrice'));
// -> 5
const listenerId = metrics.addMetricListener('highestPrice', () => {
console.log(metrics.getMetric('highestPrice'));
});
store.setCell('species', 'horse', 'price', 20);
// -> 20
metrics.delListener(listenerId);
metrics.destroy();
See also
- Using Metrics guides
- Rolling Dice demos
- Country demo
- Todo App demos
Since
v1.0.0
Getter methods
getStore
The getStore method returns a reference to the underlying Store that is backing this Metrics object.
getStore(): StoreExample
This example creates a Metrics object against a newly-created Store and then gets its reference in order to update its data.
import {createMetrics, createStore} from 'tinybase';
const metrics = createMetrics(createStore());
metrics.setMetricDefinition('speciesCount', 'species');
metrics.getStore().setCell('species', 'dog', 'price', 5);
console.log(metrics.getMetric('speciesCount'));
// -> 1
Since
v1.0.0
getTableId
The getTableId method returns the Id of the underlying Table that is backing a Metric.
getTableId(metricId: string): undefined | string| Type | Description | |
|---|---|---|
metricId | string | |
| returns | undefined | string |
If the Metric Id is invalid, the method returns undefined.
Example
This example creates a Metrics object, a single Metric definition, and then queries it (and a non-existent definition) to get the underlying Table Id.
import {createMetrics, createStore} from 'tinybase';
const metrics = createMetrics(createStore());
metrics.setMetricDefinition('speciesCount', 'species');
console.log(metrics.getTableId('speciesCount'));
// -> 'species'
console.log(metrics.getTableId('petsCount'));
// -> undefined
Since
v1.0.0
getMetric
The getMetric method gets the current value of a Metric.
getMetric(metricId: string): undefined | number| Type | Description | |
|---|---|---|
metricId | string | |
| returns | undefined | number | The numeric value of the |
If the identified Metric does not exist (or if the definition references a Table or Cell value that does not exist) then undefined is returned.
Example
This example creates a Store, creates a Metrics object, and defines a simple Metric to average the price values in the Table. It then uses getMetric to access its value (and also the value of a Metric that has not been defined).
import {createMetrics, createStore} from 'tinybase';
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
console.log(metrics.getMetric('highestPrice'));
// -> 5
console.log(metrics.getMetric('lowestPrice'));
// -> undefined
Since
v1.0.0
getMetricIds
The getMetricIds method returns an array of the Metric Ids registered with this Metrics object.
getMetricIds(): IdsExample
This example creates a Metrics object with two definitions, and then gets the Ids of the definitions.
import {createMetrics, createStore} from 'tinybase';
const metrics = createMetrics(createStore())
.setMetricDefinition('speciesCount', 'species')
.setMetricDefinition('petsCount', 'pets');
console.log(metrics.getMetricIds());
// -> ['speciesCount', 'petsCount']
Since
v1.0.0
hasMetric
The hasMetric method returns a boolean indicating whether a given Metric exists in the Metrics object, and has a value.
hasMetric(metricId: string): boolean| Type | Description | |
|---|---|---|
metricId | string | |
| returns | boolean |
Example
This example shows two simple Metric existence checks.
import {createMetrics, createStore} from 'tinybase';
const store = createStore();
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
console.log(metrics.hasMetric('lowestPrice'));
// -> false
console.log(metrics.hasMetric('highestPrice'));
// -> false
store.setTable('species', {dog: {price: 5}, cat: {price: 4}});
console.log(metrics.hasMetric('highestPrice'));
// -> true
Since
v1.0.0
Listener methods
addMetricIdsListener
The addMetricIdsListener method registers a listener function with the Metrics object that will be called whenever a Metric definition is added or removed.
addMetricIdsListener(listener: MetricIdsListener): string| Type | Description | |
|---|---|---|
listener | MetricIdsListener | The function that will be called whenever a |
| returns | string |
The provided listener is a MetricIdsListener function, and will be called with a reference to the Metrics object.
Example
This example creates a Store, a Metrics object, and then registers a listener that responds to the addition and the removal of a Metric definition.
import {createMetrics, createStore} from 'tinybase';
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
const listenerId = metrics.addMetricIdsListener((metrics) => {
console.log(metrics.getMetricIds());
});
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
// -> ['highestPrice']
metrics.delMetricDefinition('highestPrice');
// -> []
metrics.delListener(listenerId);
Since
v4.1.0
addMetricListener
The addMetricListener method registers a listener function with the Metrics object that will be called whenever the value of a specified Metric changes.
addMetricListener(
metricId: IdOrNull,
listener: MetricListener,
): string| Type | Description | |
|---|---|---|
metricId | IdOrNull | |
listener | MetricListener | The function that will be called whenever the |
| returns | string | A unique |
You can either listen to a single Metric (by specifying the Metric Id as the method's first parameter), or changes to any Metric (by providing a null wildcard).
The provided listener is a MetricListener function, and will be called with a reference to the Metrics object, the Id of the Metric that changed, the new Metric value, and the old Metric value.
Examples
This example creates a Store, a Metrics object, and then registers a listener that responds to any changes to a specific Metric.
import {createMetrics, createStore} from 'tinybase';
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
const listenerId = metrics.addMetricListener(
'highestPrice',
(metrics, _metricId, newMetric, oldMetric) => {
console.log('highestPrice metric changed');
console.log([oldMetric, newMetric]);
},
);
store.setCell('species', 'horse', 'price', 20);
// -> 'highestPrice metric changed'
// -> [5, 20]
metrics.delListener(listenerId);
This example creates a Store, a Metrics object, and then registers a listener that responds to any changes to any Metric.
import {createMetrics, createStore} from 'tinybase';
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store)
.setMetricDefinition('highestPrice', 'species', 'max', 'price')
.setMetricDefinition('speciesCount', 'species');
const listenerId = metrics.addMetricListener(
null,
(metrics, metricId, newMetric, oldMetric) => {
console.log(`${metricId} metric changed`);
console.log([oldMetric, newMetric]);
},
);
store.setCell('species', 'horse', 'price', 20);
// -> 'highestPrice metric changed'
// -> [5, 20]
// -> 'speciesCount metric changed'
// -> [3, 4]
metrics.delListener(listenerId);
Since
v1.0.0
delListener
The delListener method removes a listener that was previously added to the Metrics object.
delListener(listenerId: string): Metrics| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | Metrics | A reference to the |
Use the Id returned by the addMetricListener method. Note that the Metrics object may re-use this Id for future listeners added to it.
Example
This example creates a Store, a Metrics object, registers a listener, and then removes it.
import {createMetrics, createStore} from 'tinybase';
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
const listenerId = metrics.addMetricListener('highestPrice', () => {
console.log('highestPrice metric changed');
});
store.setCell('species', 'horse', 'price', 20);
// -> 'highestPrice metric changed'
metrics.delListener(listenerId);
store.setCell('species', 'giraffe', 'price', 50);
// -> undefined
// The listener is not called.
Since
v1.0.0
Configuration methods
delMetricDefinition
The delMetricDefinition method removes an existing Metric definition.
delMetricDefinition(metricId: string): MetricsExample
This example creates a Store, creates a Metrics object, defines a simple Metric, and then removes it.
import {createMetrics, createStore} from 'tinybase';
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('speciesCount', 'species');
console.log(metrics.getMetricIds());
// -> ['speciesCount']
metrics.delMetricDefinition('speciesCount');
console.log(metrics.getMetricIds());
// -> []
Since
v1.0.0
setMetricDefinition
The setMetricDefinition method lets you set the definition of a Metric.
setMetricDefinition(
metricId: string,
tableId: string,
aggregate?: MetricAggregate | "sum" | "avg" | "min" | "max",
getNumber?: string | (getCell: GetCell, rowId: string) => number,
aggregateAdd?: MetricAggregateAdd,
aggregateRemove?: MetricAggregateRemove,
aggregateReplace?: MetricAggregateReplace,
): Metrics| Type | Description | |
|---|---|---|
metricId | string | |
tableId | string | |
aggregate? | MetricAggregate | "sum" | "avg" | "min" | "max" | Either a string representing one of a set of common aggregation techniques ('sum', 'avg', 'min', or 'max'), or a function that aggregates numeric values from each |
getNumber? | string | (getCell: GetCell, rowId: string) => number | Either the |
aggregateAdd? | MetricAggregateAdd | A function that can be used to optimize a custom |
aggregateRemove? | MetricAggregateRemove | A function that can be used to optimize a custom |
aggregateReplace? | MetricAggregateReplace | A function that can be used to optimize a custom |
| returns | Metrics | A reference to the |
Every Metric definition is identified by a unique Id, and if you re-use an existing Id with this method, the previous definition is overwritten.
A Metric is an aggregation of numeric values produced from each Row within a single Table. Therefore the definition must specify the Table (by its Id) to be aggregated.
Without the third aggregate parameter, the Metric will simply be a count of the number of Row objects in the Table. But often you will specify a more interesting aggregate - such as the four predefined aggregates, 'sum', 'avg', 'min', and 'max' - or a custom function that produces your own aggregation of an array of numbers.
The fourth getNumber parameter specifies which Cell in each Row contains the numerical values to be used in the aggregation. Alternatively, a custom function can be provided that produces your own numeric value from the local Row as a whole.
The final three parameters, aggregateAdd, aggregateRemove, aggregateReplace need only be provided when you are using your own custom aggregate function. These give you the opportunity to reduce your custom function's algorithmic complexity by providing shortcuts that can nudge an aggregation result when a single value is added, removed, or replaced in the input values.
Examples
This example creates a Store, creates a Metrics object, and defines a simple Metric to count the Row objects in the Table.
import {createMetrics, createStore} from 'tinybase';
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('speciesCount', 'species');
console.log(metrics.getMetric('speciesCount'));
// -> 3
This example creates a Store, creates a Metrics object, and defines a standard Metric to get the highest value of each price Cell in the Row objects in the Table.
import {createMetrics, createStore} from 'tinybase';
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
console.log(metrics.getMetric('highestPrice'));
// -> 5
This example creates a Store, creates a Metrics object, and defines a custom Metric to get the lowest value of each price Cell, greater than 2.
import {createMetrics, createStore} from 'tinybase';
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition(
'lowestPriceOver2',
'species',
(numbers) => Math.min(...numbers.filter((number) => number > 2)),
'price',
);
console.log(metrics.getMetric('lowestPriceOver2'));
// -> 4
This example also creates a Store, creates a Metrics object, and defines a custom Metric to get the lowest value of each price Cell, greater than 2. However, it also reduces algorithmic complexity with two shortcut functions.
import {createMetrics, createStore} from 'tinybase';
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition(
'lowestPriceOver2',
'species',
(numbers) => Math.min(...numbers.filter((number) => number > 2)),
'price',
(metric, add) => (add > 2 ? Math.min(metric, add) : metric),
(metric, remove) => (remove == metric ? undefined : metric),
(metric, add, remove) =>
remove == metric
? undefined
: add > 2
? Math.min(metric, add)
: metric,
);
console.log(metrics.getMetric('lowestPriceOver2'));
// -> 4
store.setRow('species', 'fish', {price: 3});
console.log(metrics.getMetric('lowestPriceOver2'));
// -> 3
This example creates a Store, creates a Metrics object, and defines a custom Metric to get the average value of a discounted price.
import {createMetrics, createStore} from 'tinybase';
const store = createStore().setTable('species', {
dog: {price: 5, discount: 0.3},
cat: {price: 4, discount: 0.2},
worm: {price: 1, discount: 0.2},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition(
'averageDiscountedPrice',
'species',
'avg',
(getCell) => getCell('price') * (1 - getCell('discount')),
);
console.log(metrics.getMetric('averageDiscountedPrice'));
// -> 2.5
Since
v1.0.0
Iterator methods
forEachMetric
The forEachMetric method takes a function that it will then call for each Metric in the Metrics object.
forEachMetric(metricCallback: MetricCallback): void| Type | Description | |
|---|---|---|
metricCallback | MetricCallback | The function that should be called for every |
| returns | void | This has no return value. |
This method is useful for iterating over all the Metrics in a functional style. The metricCallback parameter is a MetricCallback function that will be called with the Id of each Metric and its value.
Example
This example iterates over each Metric in a Metrics object.
import {createMetrics, createStore} from 'tinybase';
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store)
.setMetricDefinition('highestPrice', 'species', 'max', 'price')
.setMetricDefinition('lowestPrice', 'species', 'min', 'price');
metrics.forEachMetric((metricId, metric) => {
console.log([metricId, metric]);
});
// -> ['highestPrice', 5]
// -> ['lowestPrice', 1]
Since
v1.0.0
Lifecycle methods
destroy
The destroy method should be called when this Metrics object is no longer used.
destroy(): voidThis guarantees that all of the listeners that the object registered with the underlying Store are removed and it can be correctly garbage collected.
Example
This example creates a Store, adds a Metrics object with a definition (that registers a RowListener with the underlying Store), and then destroys it again, removing the listener.
import {createMetrics, createStore} from 'tinybase';
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('speciesCount', 'species');
console.log(store.getListenerStats().row);
// -> 1
metrics.destroy();
console.log(store.getListenerStats().row);
// -> 0
Since
v1.0.0
Development methods
getListenerStats
The getListenerStats method provides a set of statistics about the listeners registered with the Metrics object, and is used for debugging purposes.
getListenerStats(): MetricsListenerStats| returns | MetricsListenerStats | A |
|---|
The method is intended to be used during development to ensure your application is not leaking listener registrations, for example.
Example
This example gets the listener statistics of a Metrics object.
import {createMetrics, createStore} from 'tinybase';
const store = createStore();
const metrics = createMetrics(store);
metrics.addMetricListener(null, () => console.log('Metric changed'));
console.log(metrics.getListenerStats());
// -> {metric: 1}
Since
v1.0.0
Functions
createMetrics
The createMetrics function creates a Metrics object, and is the main entry point into the metrics module.
createMetrics(store: Store): MetricsA given Store can only have one Metrics object associated with it. If you call this function twice on the same Store, your second call will return a reference to the Metrics object created by the first.
Examples
This example creates a Metrics object.
import {createMetrics, createStore} from 'tinybase';
const store = createStore();
const metrics = createMetrics(store);
console.log(metrics.getMetricIds());
// -> []
This example creates a Metrics object, and calls the method a second time for the same Store to return the same object.
import {createMetrics, createStore} from 'tinybase';
const store = createStore();
const metrics1 = createMetrics(store);
const metrics2 = createMetrics(store);
console.log(metrics1 === metrics2);
// -> true
Since
v1.0.0
Type Aliases
Listener type aliases
MetricIdsListener
The MetricIdsListener type describes a function that is used to listen to Metric definitions being added or removed.
(metrics: Metrics): void| Type | Description | |
|---|---|---|
metrics | Metrics | A reference to the |
| returns | void | This has no return value. |
A MetricIdsListener is provided when using the addMetricIdsListener method. See that method for specific examples.
When called, a MetricIdsListener is given a reference to the Metrics object.
Since
v1.0.0
MetricListener
The MetricListener type describes a function that is used to listen to changes to a Metric.
(
metrics: Metrics,
metricId: Id,
newMetric: Metric | undefined,
oldMetric: Metric | undefined,
): void| Type | Description | |
|---|---|---|
metrics | Metrics | A reference to the |
metricId | Id | |
newMetric | Metric | undefined | The new value of the |
oldMetric | Metric | undefined | The old value of the |
| returns | void | This has no return value. |
A MetricListener is provided when using the addMetricListener method. See that method for specific examples.
When called, a MetricListener is given a reference to the Metrics object, the Id of the Metric that changed, and the new and old values of the Metric.
If this is the first time that a Metric has had a value (such as when a table has gained its first row), the old value will be undefined. If a Metric now no longer has a value, the new value will be undefined.
Since
v1.0.0
Aggregators type aliases
MetricAggregate
The MetricAggregate type describes a custom function that takes an array of numbers and returns an aggregate that is used as a Metric.
(
numbers: number[],
length: number,
): Metric| Type | Description | |
|---|---|---|
numbers | number[] | The array of numbers in the |
length | number | The length of the array of numbers in the |
| returns | Metric | The value of the |
There are a number of common predefined aggregators, such as for counting, summing, and averaging values. This type is instead used for when you wish to use a more complex aggregation of your own devising. See the setMetricDefinition method for more examples.
Since
v1.0.0
MetricAggregateAdd
The MetricAggregateAdd type describes a function that can be used to optimize a custom MetricAggregate by providing a shortcut for when a single value is added to the input values.
(
metric: Metric,
add: number,
length: number,
): Metric | undefined| Type | Description | |
|---|---|---|
metric | Metric | The current value of the |
add | number | The number being added to the |
length | number | The length of the array of numbers in the |
| returns | Metric | undefined | The new value of the |
Some aggregation functions do not need to recalculate the aggregation of the whole set when one value changes. For example, when adding a new number to a series, the new sum of the series is the new value added to the previous sum.
If it is not possible to shortcut the aggregation based on just one value being added, return undefined and the Metric will be completely recalculated.
When possible, if you are providing a custom MetricAggregate, seek an implementation of an MetricAggregateAdd function that can reduce the complexity cost of growing the input data set. See the setMetricDefinition method for more examples.
Since
v1.0.0
MetricAggregateRemove
The MetricAggregateRemove type describes a function that can be used to optimize a custom MetricAggregate by providing a shortcut for when a single value is removed from the input values.
(
metric: Metric,
remove: number,
length: number,
): Metric | undefined| Type | Description | |
|---|---|---|
metric | Metric | The current value of the |
remove | number | The number being removed from the |
length | number | The length of the array of numbers in the |
| returns | Metric | undefined | The new value of the |
Some aggregation functions do not need to recalculate the aggregation of the whole set when one value changes. For example, when removing a number from a series, the new sum of the series is the new value subtracted from the previous sum.
If it is not possible to shortcut the aggregation based on just one value being removed, return undefined and the Metric will be completely recalculated. One example might be if you were taking the minimum of the values, and the previous minimum is being removed. The whole of the rest of the list will need to be re-scanned to find a new minimum.
When possible, if you are providing a custom MetricAggregate, seek an implementation of an MetricAggregateRemove function that can reduce the complexity cost of shrinking the input data set. See the setMetricDefinition method for more examples.
Since
v1.0.0
MetricAggregateReplace
The MetricAggregateReplace type describes a function that can be used to optimize a custom MetricAggregate by providing a shortcut for when a single value in the input values is replaced with another.
(
metric: Metric,
add: number,
remove: number,
length: number,
): Metric | undefined| Type | Description | |
|---|---|---|
metric | Metric | The current value of the |
add | number | The number being added to the |
remove | number | The number being removed from the |
length | number | The length of the array of numbers in the |
| returns | Metric | undefined | The new value of the |
Some aggregation functions do not need to recalculate the aggregation of the whole set when one value changes. For example, when replacing a number in a series, the new sum of the series is the previous sum, plus the new value, minus the old value.
If it is not possible to shortcut the aggregation based on just one value changing, return undefined and the Metric will be completely recalculated.
When possible, if you are providing a custom MetricAggregate, seek an implementation of an MetricAggregateReplace function that can reduce the complexity cost of changing the input data set in place. See the setMetricDefinition method for more examples.
Since
v1.0.0
Callback type aliases
MetricCallback
The MetricCallback type describes a function that takes a Metric's Id and a callback to loop over each Row within it.
(
metricId: Id,
metric?: Metric,
): voidA MetricCallback is provided when using the forEachMetric method, so that you can do something based on every Metric in the Metrics object. See that method for specific examples.
Since
v1.0.0
Metric type aliases
Metric
The Metric type is simply an alias, but represents a number formed by aggregating multiple other numbers together.
numberSince
v1.0.0
Development type aliases
MetricsListenerStats
The MetricsListenerStats type describes the number of listeners registered with the Metrics object, and can be used for debugging purposes.
{metric: number}| Type | Description | |
|---|---|---|
metric | number | The number of |
A MetricsListenerStats object is returned from the getListenerStats method.
Since
v1.0.0
indexes
The indexes module of the TinyBase project provides the ability to create and track indexes of the data in Store objects.
The main entry point to this module is the createIndexes function, which returns a new Indexes object. From there, you can create new Index definitions, access the contents of those Indexes directly, and register listeners for when they change.
Since
v1.0.0
Interfaces
Indexes
An Indexes object lets you look up all the Row objects in a Table that have a certain Cell value.
This is useful for creating filtered views of a Table, or simple search functionality.
Create an Indexes object easily with the createIndexes function. From there, you can add new Index definitions (with the setIndexDefinition method), query their contents (with the getSliceIds method and getSliceRowIds method), and add listeners for when they change (with the addSliceIdsListener method and addSliceRowIdsListener method).
This module defaults to indexing Row objects by one of their Cell values. However, far more complex indexes can be configured with a custom function.
Example
This example shows a very simple lifecycle of an Indexes object: from creation, to adding a definition, getting its contents, and then registering and removing a listener for it.
import {createIndexes, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition(
'bySpecies', // indexId
'pets', // tableId to index
'species', // cellId to index
);
console.log(indexes.getSliceIds('bySpecies'));
// -> ['dog', 'cat']
console.log(indexes.getSliceRowIds('bySpecies', 'dog'));
// -> ['fido', 'cujo']
const listenerId = indexes.addSliceIdsListener('bySpecies', () => {
console.log(indexes.getSliceIds('bySpecies'));
});
store.setRow('pets', 'lowly', {species: 'worm'});
// -> ['dog', 'cat', 'worm']
indexes.delListener(listenerId);
indexes.destroy();
See also
- Using Indexes guides
- Rolling Dice demos
- Country demo
- Todo App demos
- Word Frequencies demo
Since
v1.0.0
Getter methods
getStore
The getStore method returns a reference to the underlying Store that is backing this Indexes object.
getStore(): StoreExample
This example creates an Indexes object against a newly-created Store and then gets its reference in order to update its data.
import {createIndexes, createStore} from 'tinybase';
const indexes = createIndexes(createStore());
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
indexes.getStore().setCell('pets', 'fido', 'species', 'dog');
console.log(indexes.getSliceIds('bySpecies'));
// -> ['dog']
Since
v1.0.0
getTableId
The getTableId method returns the Id of the underlying Table that is backing an Index.
getTableId(indexId: string): undefined | string| Type | Description | |
|---|---|---|
indexId | string | |
| returns | undefined | string |
If the Index Id is invalid, the method returns undefined.
Example
This example creates an Indexes object, a single Index definition, and then queries it (and a non-existent definition) to get the underlying Table Id.
import {createIndexes, createStore} from 'tinybase';
const indexes = createIndexes(createStore());
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
console.log(indexes.getTableId('bySpecies'));
// -> 'pets'
console.log(indexes.getTableId('byColor'));
// -> undefined
Since
v1.0.0
getSliceRowIds
The getSliceRowIds method gets the list of Row Ids in a given Slice, within a given Index.
getSliceRowIds(
indexId: string,
sliceId: string,
): Ids| Type | Description | |
|---|---|---|
indexId | string | |
sliceId | string | |
| returns | Ids |
If the identified Index or Slice do not exist (or if the definition references a Table that does not exist) then an empty array is returned.
Example
This example creates a Store, creates an Indexes object, and defines a simple Index. It then uses getSliceRowIds to see the Row Ids in the Slice (and also the Row Ids in Slices that do not exist).
import {createIndexes, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
console.log(indexes.getSliceRowIds('bySpecies', 'dog'));
// -> ['fido', 'cujo']
console.log(indexes.getSliceRowIds('bySpecies', 'worm'));
// -> []
console.log(indexes.getSliceRowIds('byColor', 'brown'));
// -> []
Since
v1.0.0
getIndexIds
The getIndexIds method returns an array of the Index Ids registered with this Indexes object.
getIndexIds(): IdsExample
This example creates an Indexes object with two definitions, and then gets the Ids of the definitions.
import {createIndexes, createStore} from 'tinybase';
const indexes = createIndexes(createStore())
.setIndexDefinition('bySpecies', 'pets', 'species')
.setIndexDefinition('byColor', 'pets', 'color');
console.log(indexes.getIndexIds());
// -> ['bySpecies', 'byColor']
Since
v1.0.0
getSliceIds
The getSliceIds method gets the list of Slice Ids in an Index.
getSliceIds(indexId: string): Ids| Type | Description | |
|---|---|---|
indexId | string | |
| returns | Ids |
If the identified Index does not exist (or if the definition references a Table that does not exist) then an empty array is returned.
Example
This example creates a Store, creates an Indexes object, and defines a simple Index. It then uses getSliceIds to see the available Slice Ids in the Index (and also the Slice Ids in an Index that has not been defined).
import {createIndexes, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
console.log(indexes.getSliceIds('bySpecies'));
// -> ['dog', 'cat']
console.log(indexes.getSliceIds('byColor'));
// -> []
Since
v1.0.0
hasIndex
The hasIndex method returns a boolean indicating whether a given Index exists in the Indexes object.
hasIndex(indexId: string): boolean| Type | Description | |
|---|---|---|
indexId | string | |
| returns | boolean |
Example
This example shows two simple Index existence checks.
import {createIndexes, createStore} from 'tinybase';
const indexes = createIndexes(createStore());
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
console.log(indexes.hasIndex('bySpecies'));
// -> true
console.log(indexes.hasIndex('byColor'));
// -> false
Since
v1.0.0
hasSlice
The hasSlice method returns a boolean indicating whether a given Slice exists in the Indexes object.
hasSlice(
indexId: string,
sliceId: string,
): boolean| Type | Description | |
|---|---|---|
indexId | string | |
sliceId | string | |
| returns | boolean |
Example
This example shows two simple Index existence checks.
import {createIndexes, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
console.log(indexes.hasSlice('bySpecies', 'dog'));
// -> true
console.log(indexes.hasSlice('bySpecies', 'worm'));
// -> false
Since
v1.0.0
Listener methods
addSliceRowIdsListener
The addSliceRowIdsListener method registers a listener function with the Indexes object that will be called whenever the Row Ids in a Slice change.
addSliceRowIdsListener(
indexId: IdOrNull,
sliceId: IdOrNull,
listener: SliceRowIdsListener,
): string| Type | Description | |
|---|---|---|
indexId | IdOrNull | |
sliceId | IdOrNull | |
listener | SliceRowIdsListener | The function that will be called whenever the |
| returns | string | A unique |
You can either listen to a single Slice (by specifying the Index Id and Slice Id as the method's first two parameters), or changes to any Slice (by providing null wildcards).
Both, either, or neither of the indexId and sliceId parameters can be wildcarded with null. You can listen to a specific Slice in a specific Index, any Slice in a specific Index, a specific Slice in any Index, or any Slice in any Index.
The provided listener is a SliceRowIdsListener function, and will be called with a reference to the Indexes object, the Id of the Index, and the Id of the Slice that changed.
Examples
This example creates a Store, an Indexes object, and then registers a listener that responds to any changes to a specific Slice.
import {createIndexes, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const listenerId = indexes.addSliceRowIdsListener(
'bySpecies',
'dog',
(indexes) => {
console.log('Row Ids for dog slice in bySpecies index changed');
console.log(indexes.getSliceRowIds('bySpecies', 'dog'));
},
);
store.setRow('pets', 'toto', {species: 'dog'});
// -> 'Row Ids for dog slice in bySpecies index changed'
// -> ['fido', 'cujo', 'toto']
indexes.delListener(listenerId);
This example creates a Store, an Indexes object, and then registers a listener that responds to any changes to any Slice.
import {createIndexes, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const indexes = createIndexes(store)
.setIndexDefinition('bySpecies', 'pets', 'species')
.setIndexDefinition('byColor', 'pets', 'color');
const listenerId = indexes.addSliceRowIdsListener(
null,
null,
(indexes, indexId, sliceId) => {
console.log(
`Row Ids for ${sliceId} slice in ${indexId} index changed`,
);
console.log(indexes.getSliceRowIds(indexId, sliceId));
},
);
store.setRow('pets', 'toto', {species: 'dog', color: 'brown'});
// -> 'Row Ids for dog slice in bySpecies index changed'
// -> ['fido', 'cujo', 'toto']
// -> 'Row Ids for brown slice in byColor index changed'
// -> ['fido', 'toto']
indexes.delListener(listenerId);
Since
v1.0.0
addIndexIdsListener
The addIndexIdsListener method registers a listener function with the Indexes object that will be called whenever an Index definition is added or removed.
addIndexIdsListener(listener: IndexIdsListener): string| Type | Description | |
|---|---|---|
listener | IndexIdsListener | The function that will be called whenever an |
| returns | string |
The provided listener is an IndexIdsListener function, and will be called with a reference to the Indexes object.
Example
This example creates a Store, an Indexes object, and then registers a listener that responds to the addition and the removal of an Index definition.
import {createIndexes, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
const listenerId = indexes.addIndexIdsListener((indexes) => {
console.log(indexes.getIndexIds());
});
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
// -> ['bySpecies']
indexes.delIndexDefinition('bySpecies');
// -> []
indexes.delListener(listenerId);
Since
v4.1.0
addSliceIdsListener
The addSliceIdsListener method registers a listener function with the Indexes object that will be called whenever the Slice Ids in an Index change.
addSliceIdsListener(
indexId: IdOrNull,
listener: SliceIdsListener,
): string| Type | Description | |
|---|---|---|
indexId | IdOrNull | |
listener | SliceIdsListener | The function that will be called whenever the |
| returns | string | A unique |
You can either listen to a single Index (by specifying the Index Id as the method's first parameter), or changes to any Index (by providing a null wildcard).
The provided listener is a SliceIdsListener function, and will be called with a reference to the Indexes object, and the Id of the Index that changed.
Examples
This example creates a Store, an Indexes object, and then registers a listener that responds to any changes to a specific Index.
import {createIndexes, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const listenerId = indexes.addSliceIdsListener('bySpecies', (indexes) => {
console.log('Slice Ids for bySpecies index changed');
console.log(indexes.getSliceIds('bySpecies'));
});
store.setRow('pets', 'lowly', {species: 'worm'});
// -> 'Slice Ids for bySpecies index changed'
// -> ['dog', 'cat', 'worm']
indexes.delListener(listenerId);
This example creates a Store, an Indexes object, and then registers a listener that responds to any changes to any Index.
import {createIndexes, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'brown'},
});
const indexes = createIndexes(store)
.setIndexDefinition('bySpecies', 'pets', 'species')
.setIndexDefinition('byColor', 'pets', 'color');
const listenerId = indexes.addSliceIdsListener(
null,
(indexes, indexId) => {
console.log(`Slice Ids for ${indexId} index changed`);
console.log(indexes.getSliceIds(indexId));
},
);
store.setRow('pets', 'lowly', {species: 'worm', color: 'pink'});
// -> 'Slice Ids for bySpecies index changed'
// -> ['dog', 'cat', 'worm']
// -> 'Slice Ids for byColor index changed'
// -> ['brown', 'black', 'pink']
indexes.delListener(listenerId);
Since
v1.0.0
delListener
The delListener method removes a listener that was previously added to the Indexes object.
delListener(listenerId: string): Indexes| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | Indexes | A reference to the |
Use the Id returned by whichever method was used to add the listener. Note that the Indexes object may re-use this Id for future listeners added to it.
Example
This example creates a Store, an Indexes object, registers a listener, and then removes it.
import {createIndexes, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const listenerId = indexes.addSliceIdsListener('bySpecies', () => {
console.log('Slice Ids for bySpecies index changed');
});
store.setRow('pets', 'lowly', {species: 'worm'});
// -> 'Slice Ids for bySpecies index changed'
indexes.delListener(listenerId);
store.setRow('pets', 'toto', {species: 'dog'});
// -> undefined
// The listener is not called.
Since
v1.0.0
Configuration methods
delIndexDefinition
The delIndexDefinition method removes an existing Index definition.
delIndexDefinition(indexId: string): IndexesExample
This example creates a Store, creates an Indexes object, defines a simple Index, and then removes it.
import {createIndexes, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
console.log(indexes.getIndexIds());
// -> ['bySpecies']
indexes.delIndexDefinition('bySpecies');
console.log(indexes.getIndexIds());
// -> []
Since
v1.0.0
setIndexDefinition
The setIndexDefinition method lets you set the definition of an Index.
setIndexDefinition(
indexId: string,
tableId: string,
getSliceIdOrIds?: string | (getCell: GetCell, rowId: string) => string | Ids,
getSortKey?: string | (getCell: GetCell, rowId: string) => SortKey,
sliceIdSorter?: (sliceId1: string, sliceId2: string) => number,
rowIdSorter?: (sortKey1: SortKey, sortKey2: SortKey, sliceId: string) => number,
): Indexes| Type | Description | |
|---|---|---|
indexId | string | |
tableId | string | |
getSliceIdOrIds? | string | (getCell: GetCell, rowId: string) => string | Ids | Either the |
getSortKey? | string | (getCell: GetCell, rowId: string) => SortKey | Either the |
sliceIdSorter? | (sliceId1: string, sliceId2: string) => number | A function that takes two |
rowIdSorter? | (sortKey1: SortKey, sortKey2: SortKey, sliceId: string) => number | A function that takes two |
| returns | Indexes | A reference to the |
Every Index definition is identified by a unique Id, and if you re-use an existing Id with this method, the previous definition is overwritten.
An Index is a keyed map of Slice objects, each of which is a list of Row Ids from a given Table. Therefore the definition must specify the Table (by its Id) to be indexed.
The Ids in a Slice represent Row objects from a Table that all have a derived string value in common, as described by this method. Those values are used as the key for each Slice in the overall Index object.
Without the third getSliceIdOrIds parameter, the Index will simply have a single Slice, keyed by an empty string. But more often you will specify a Cell value containing the Slice Id that the Row should belong to. Alternatively, a custom function can be provided that produces your own Slice Id from the local Row as a whole. Since v2.1, the custom function can return an array of Slice Ids, each of which the Row will then belong to.
The fourth getSortKey parameter specifies a Cell Id to get a value (or a function that processes a whole Row to get a value) that is used to sort the Row Ids within each Slice in the Index.
The fifth parameter, sliceIdSorter, lets you specify a way to sort the Slice Ids when you access the Index, which may be useful if you are trying to create an alphabetic Index of Row entries. If not specified, the order of the Slice Ids will match the order of Row insertion.
The final parameter, rowIdSorter, lets you specify a way to sort the Row Ids within each Slice, based on the getSortKey parameter. This may be useful if you are trying to keep Rows in a determined order relative to each other in the Index. If omitted, the Row Ids are sorted alphabetically, based on the getSortKey parameter.
The two 'sorter' parameters, sliceIdSorter and rowIdSorter, are functions that take two values and return a positive or negative number for when they are in the wrong or right order, respectively. This is exactly the same as the 'compareFunction' that is used in the standard JavaScript array sort method, with the addition that rowIdSorter also takes the Slice Id parameter, in case you want to sort Row Ids differently in each Slice. You can use the convenient defaultSorter function to default this to be alphanumeric.
Examples
This example creates a Store, creates an Indexes object, and defines a simple Index based on the values in the species Cell.
import {createIndexes, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
console.log(indexes.getSliceIds('bySpecies'));
// -> ['dog', 'cat']
console.log(indexes.getSliceRowIds('bySpecies', 'dog'));
// -> ['fido', 'cujo']
This example creates a Store, creates an Indexes object, and defines an Index based on the first letter of the pets' names.
import {createIndexes, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('byFirst', 'pets', (_, rowId) => rowId[0]);
console.log(indexes.getSliceIds('byFirst'));
// -> ['f', 'c']
console.log(indexes.getSliceRowIds('byFirst', 'f'));
// -> ['fido', 'felix']
This example creates a Store, creates an Indexes object, and defines an Index based on each of the letters present in the pets' names.
import {createIndexes, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
rex: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('containsLetter', 'pets', (_, rowId) =>
rowId.split(''),
);
console.log(indexes.getSliceIds('containsLetter'));
// -> ['f', 'i', 'd', 'o', 'e', 'l', 'x', 'r']
console.log(indexes.getSliceRowIds('containsLetter', 'i'));
// -> ['fido', 'felix']
console.log(indexes.getSliceRowIds('containsLetter', 'x'));
// -> ['felix', 'rex']
This example creates a Store, creates an Indexes object, and defines an Index based on the first letter of the pets' names. The Slice Ids (and Row Ids within them) are alphabetically sorted.
import {createIndexes, createStore, defaultSorter} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition(
'byFirst', // indexId
'pets', // tableId
(_, rowId) => rowId[0], // each Row's sliceId
(_, rowId) => rowId, // each Row's sort key
defaultSorter, // sort Slice Ids
defaultSorter, // sort Row Ids
);
console.log(indexes.getSliceIds('byFirst'));
// -> ['c', 'f']
console.log(indexes.getSliceRowIds('byFirst', 'f'));
// -> ['felix', 'fido']
Since
v1.0.0
Iterator methods
forEachIndex
The forEachIndex method takes a function that it will then call for each Index in a specified Indexes object.
forEachIndex(indexCallback: IndexCallback): void| Type | Description | |
|---|---|---|
indexCallback | IndexCallback | The function that should be called for every |
| returns | void | This has no return value. |
This method is useful for iterating over the structure of the Indexes object in a functional style. The indexCallback parameter is a IndexCallback function that will be called with the Id of each Index, and with a function that can then be used to iterate over each Slice of the Index, should you wish.
Example
This example iterates over each Index in an Indexes object, and lists each Slice Id within them.
import {createIndexes, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const indexes = createIndexes(store)
.setIndexDefinition('bySpecies', 'pets', 'species')
.setIndexDefinition('byColor', 'pets', 'color');
indexes.forEachIndex((indexId, forEachSlice) => {
console.log(indexId);
forEachSlice((sliceId) => console.log(`- ${sliceId}`));
});
// -> 'bySpecies'
// -> '- dog'
// -> '- cat'
// -> 'byColor'
// -> '- brown'
// -> '- black'
Since
v1.0.0
forEachSlice
The forEachSlice method takes a function that it will then call for each Slice in a specified Index.
forEachSlice(
indexId: string,
sliceCallback: SliceCallback,
): void| Type | Description | |
|---|---|---|
indexId | string | |
sliceCallback | SliceCallback | The function that should be called for every |
| returns | void | This has no return value. |
This method is useful for iterating over the Slice structure of the Index in a functional style. The rowCallback parameter is a RowCallback function that will be called with the Id and value of each Row in the Slice.
Example
This example iterates over each Row in a Slice, and lists its Id.
import {createIndexes, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
indexes.forEachSlice('bySpecies', (sliceId, forEachRow) => {
console.log(sliceId);
forEachRow((rowId) => console.log(`- ${rowId}`));
});
// -> 'dog'
// -> '- fido'
// -> '- cujo'
// -> 'cat'
// -> '- felix'
Since
v1.0.0
Lifecycle methods
destroy
The destroy method should be called when this Indexes object is no longer used.
destroy(): voidThis guarantees that all of the listeners that the object registered with the underlying Store are removed and it can be correctly garbage collected.
Example
This example creates a Store, adds an Indexes object with a definition (that registers a RowListener with the underlying Store), and then destroys it again, removing the listener.
import {createIndexes, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
console.log(store.getListenerStats().row);
// -> 1
indexes.destroy();
console.log(store.getListenerStats().row);
// -> 0
Since
v1.0.0
Development methods
getListenerStats
The getListenerStats method provides a set of statistics about the listeners registered with the Indexes object, and is used for debugging purposes.
getListenerStats(): IndexesListenerStats| returns | IndexesListenerStats | A |
|---|
The IndexesListenerStats object contains a breakdown of the different types of listener.
The method is intended to be used during development to ensure your application is not leaking listener registrations, for example.
Example
This example gets the listener statistics of an Indexes object.
import {createIndexes, createStore} from 'tinybase';
const store = createStore();
const indexes = createIndexes(store);
indexes.addSliceIdsListener(null, () => {
console.log('Slice Ids changed');
});
indexes.addSliceRowIdsListener(null, null, () => {
console.log('Slice Row Ids changed');
});
console.log(indexes.getListenerStats());
// -> {sliceIds: 1, sliceRowIds: 1}
Since
v1.0.0
Functions
createIndexes
The createIndexes function creates an Indexes object, and is the main entry point into the indexes module.
createIndexes(store: Store): IndexesA given Store can only have one Indexes object associated with it. If you call this function twice on the same Store, your second call will return a reference to the Indexes object created by the first.
Examples
This example creates an Indexes object.
import {createIndexes, createStore} from 'tinybase';
const store = createStore();
const indexes = createIndexes(store);
console.log(indexes.getIndexIds());
// -> []
This example creates an Indexes object, and calls the method a second time for the same Store to return the same object.
import {createIndexes, createStore} from 'tinybase';
const store = createStore();
const indexes1 = createIndexes(store);
const indexes2 = createIndexes(store);
console.log(indexes1 === indexes2);
// -> true
Since
v1.0.0
Type Aliases
Listener type aliases
SliceRowIdsListener
The SliceRowIdsListener type describes a function that is used to listen to changes to the Row Ids in a Slice.
(
indexes: Indexes,
indexId: Id,
sliceId: Id,
): void| Type | Description | |
|---|---|---|
indexes | Indexes | A reference to the |
indexId | Id | |
sliceId | Id | |
| returns | void | This has no return value. |
A SliceRowIdsListener is provided when using the addSliceRowIdsListener method. See that method for specific examples.
When called, a SliceRowIdsListener is given a reference to the Indexes object, the Id of the Index that changed, and the Id of the Slice whose Row Ids changed.
Since
v1.0.0
IndexIdsListener
The IndexIdsListener type describes a function that is used to listen to Index definitions being added or removed.
(indexes: Indexes): void| Type | Description | |
|---|---|---|
indexes | Indexes | A reference to the |
| returns | void | This has no return value. |
A IndexIdsListener is provided when using the addIndexIdsListener method. See that method for specific examples.
When called, an IndexIdsListener is given a reference to the Indexes object.
Since
v1.0.0
SliceIdsListener
The SliceIdsListener type describes a function that is used to listen to changes to the Slice Ids in an Index.
(
indexes: Indexes,
indexId: Id,
): void| Type | Description | |
|---|---|---|
indexes | Indexes | A reference to the |
indexId | Id | |
| returns | void | This has no return value. |
A SliceIdsListener is provided when using the addSliceIdsListener method. See that method for specific examples.
When called, a SliceIdsListener is given a reference to the Indexes object, and the Id of the Index that changed.
Since
v1.0.0
Callback type aliases
IndexCallback
The IndexCallback type describes a function that takes an Index's Id and a callback to loop over each Slice within it.
(
indexId: Id,
forEachSlice: (sliceCallback: SliceCallback) => void,
): void| Type | Description | |
|---|---|---|
indexId | Id | |
forEachSlice | (sliceCallback: SliceCallback) => void | A function that will let you iterate over the |
| returns | void | This has no return value. |
A IndexCallback is provided when using the forEachIndex method, so that you can do something based on every Index in the Indexes object. See that method for specific examples.
Since
v1.0.0
SliceCallback
The SliceCallback type describes a function that takes a Slice's Id and a callback to loop over each Row within it.
(
sliceId: Id,
forEachRow: (rowCallback: RowCallback) => void,
): void| Type | Description | |
|---|---|---|
sliceId | Id | |
forEachRow | (rowCallback: RowCallback) => void | A function that will let you iterate over the |
| returns | void | This has no return value. |
A SliceCallback is provided when using the forEachSlice method, so that you can do something based on every Slice in an Index. See that method for specific examples.
Since
v1.0.0
Concept type aliases
Index
The Index type represents the concept of a map of Slice objects, keyed by Id.
{[sliceId: Id]: Slice}The Ids in a Slice represent Row objects from a Table that all have a derived string value in common, as described by the setIndexDefinition method. Those values are used as the key for each Slice in the overall Index object.
Note that the Index type is not actually used in the API, and you instead enumerate and access its structure with the getSliceIds method and getSliceRowIds method.
Since
v1.0.0
Slice
The Slice type represents the concept of a set of Row objects that comprise part of an Index.
IdsThe Ids in a Slice represent Row objects from a Table that all have a derived string value in common, as described by the setIndexDefinition method.
Note that the Slice type is not actually used in the API, and you instead get Row Ids directly with the getSliceRowIds method.
Since
v1.0.0
Development type aliases
IndexesListenerStats
The IndexesListenerStats type describes the number of listeners registered with the Indexes object, and can be used for debugging purposes.
{
sliceIds: number;
sliceRowIds: number;
}| Type | Description | |
|---|---|---|
sliceIds | number | The number of SlideIdsListener functions registered with the |
sliceRowIds | number | The number of |
A IndexesListenerStats object is returned from the getListenerStats method.
Since
v1.0.0
relationships
The relationships module of the TinyBase project provides the ability to create and track relationships between the data in Store objects.
The main entry point to this module is the createRelationships function, which returns a new Relationships object. From there, you can create new Relationship definitions, access the associations within those Relationships directly, and register listeners for when they change.
Since
v1.0.0
Interfaces
Relationships
A Relationships object lets you associate a Row in a one Table with the Id of a Row in another Table.
This is useful for creating parent-child relationships between the data in different Table objects, but it can also be used to model a linked list of Row objects in the same Table.
Create a Relationships object easily with the createRelationships function. From there, you can add new Relationship definitions (with the setRelationshipDefinition method), query their contents (with the getRemoteRowId method, the getLocalRowIds method, and the getLinkedRowIds method), and add listeners for when they change (with the addRemoteRowIdListener method, the addLocalRowIdsListener method, and the addLinkedRowIdsListener method).
This module defaults to creating relationships between Row objects by using one of their Cell values. However, far more complex relationships can be configured with a custom function.
Example
This example shows a very simple lifecycle of a Relationships object: from creation, to adding definitions (both local/remote table and linked list), getting their contents, and then registering and removing listeners for them.
import {createRelationships, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', next: 'felix'},
felix: {species: 'cat', next: 'cujo'},
cujo: {species: 'dog'},
})
.setTable('species', {
dog: {price: 5},
cat: {price: 4},
});
const relationships = createRelationships(store);
// A local/remote table relationship:
relationships.setRelationshipDefinition(
'petSpecies', // relationshipId
'pets', // localTableId to link from
'species', // remoteTableId to link to
'species', // cellId containing remote key
);
console.log(relationships.getRemoteRowId('petSpecies', 'fido'));
// -> 'dog'
console.log(relationships.getLocalRowIds('petSpecies', 'dog'));
// -> ['fido', 'cujo']
// A linked list relationship:
relationships.setRelationshipDefinition(
'petSequence', // relationshipId
'pets', // localTableId to link from
'pets', // the same remoteTableId to link within
'next', // cellId containing link key
);
console.log(relationships.getLinkedRowIds('petSequence', 'fido'));
// -> ['fido', 'felix', 'cujo']
const listenerId1 = relationships.addLocalRowIdsListener(
'petSpecies',
'dog',
() => {
console.log('petSpecies relationship (to dog) changed');
console.log(relationships.getLocalRowIds('petSpecies', 'dog'));
},
);
const listenerId2 = relationships.addLinkedRowIdsListener(
'petSequence',
'fido',
() => {
console.log('petSequence linked list (from fido) changed');
console.log(relationships.getLinkedRowIds('petSequence', 'fido'));
},
);
store.setRow('pets', 'toto', {species: 'dog'});
// -> 'petSpecies relationship (to dog) changed'
// -> ['fido', 'cujo', 'toto']
store.setCell('pets', 'cujo', 'next', 'toto');
// -> 'petSequence linked list (from fido) changed'
// -> ['fido', 'felix', 'cujo', 'toto']
relationships.delListener(listenerId1);
relationships.delListener(listenerId2);
relationships.destroy();
See also
- Using Relationships guide
- Drawing demo
Since
v1.0.0
Getter methods
getStore
The getStore method returns a reference to the underlying Store that is backing this Relationships object.
getStore(): StoreExample
This example creates a Relationships object against a newly-created Store and then gets its reference in order to update its data.
import {createRelationships, createStore} from 'tinybase';
const relationships = createRelationships(createStore());
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
relationships.getStore().setCell('pets', 'fido', 'species', 'dog');
console.log(relationships.getRemoteRowId('petSpecies', 'fido'));
// -> 'dog'
Since
v1.0.0
getLocalTableId
The getLocalTableId method returns the Id of the underlying local Table that is used in the Relationship.
getLocalTableId(relationshipId: string): undefined | string| Type | Description | |
|---|---|---|
relationshipId | string | The |
| returns | undefined | string | The |
If the Relationship Id is invalid, the method returns undefined.
Example
This example creates a Relationship object, a single Relationship definition, and then queries it (and a non-existent definition) to get the underlying local Table Id.
import {createRelationships, createStore} from 'tinybase';
const relationships = createRelationships(createStore());
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
console.log(relationships.getLocalTableId('petSpecies'));
// -> 'pets'
console.log(relationships.getLocalTableId('petColor'));
// -> undefined
Since
v1.0.0
getRemoteTableId
The getRemoteTableId method returns the Id of the underlying remote Table that is used in the Relationship.
getRemoteTableId(relationshipId: string): undefined | string| Type | Description | |
|---|---|---|
relationshipId | string | The |
| returns | undefined | string | The |
If the Relationship Id is invalid, the method returns undefined.
Example
This example creates a Relationship object, a single Relationship definition, and then queries it (and a non-existent definition) to get the underlying remote Table Id.
import {createRelationships, createStore} from 'tinybase';
const relationships = createRelationships(createStore());
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
console.log(relationships.getRemoteTableId('petSpecies'));
// -> 'species'
console.log(relationships.getRemoteTableId('petColor'));
// -> undefined
Since
v1.0.0
getLinkedRowIds
The getLinkedRowIds method gets the linked Row Ids for a given Row in a linked list Relationship.
getLinkedRowIds(
relationshipId: string,
firstRowId: string,
): Ids| Type | Description | |
|---|---|---|
relationshipId | string | The |
firstRowId | string | The |
| returns | Ids | The linked |
A linked list Relationship is one that has the same Table specified as both local Table Id and remote Table Id, allowing you to create a sequence of Row objects within that one Table.
If the identified Relationship or Row does not exist (or if the definition references a Table that does not exist) then an array containing just the first Row Id is returned.
Example
This example creates a Store, creates a Relationships object, and defines a simple linked list Relationship. It then uses getLinkedRowIds to see the linked Row Ids in the Relationship (and also the linked Row Ids for a Row that does not exist, and for a Relationship that has not been defined).
import {createRelationships, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', next: 'felix'},
felix: {species: 'cat', next: 'cujo'},
cujo: {species: 'dog'},
});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSequence',
'pets',
'pets',
'next',
);
console.log(relationships.getLinkedRowIds('petSequence', 'fido'));
// -> ['fido', 'felix', 'cujo']
console.log(relationships.getLinkedRowIds('petSequence', 'felix'));
// -> ['felix', 'cujo']
console.log(relationships.getLinkedRowIds('petSequence', 'toto'));
// -> ['toto']
console.log(relationships.getLinkedRowIds('petFriendships', 'fido'));
// -> ['fido']
Since
v1.0.0
getLocalRowIds
The getLocalRowIds method gets the local Row Ids for a given remote Row in a Relationship.
getLocalRowIds(
relationshipId: string,
remoteRowId: string,
): Ids| Type | Description | |
|---|---|---|
relationshipId | string | The |
remoteRowId | string | The |
| returns | Ids | The local |
If the identified Relationship or Row does not exist (or if the definition references a Table that does not exist) then an empty array is returned.
Example
This example creates a Store, creates a Relationships object, and defines a simple Relationship. It then uses getLocalRowIds to see the local Row Ids in the Relationship (and also the local Row Ids for a remote Row that does not exist, and for a Relationship that has not been defined).
import {createRelationships, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
})
.setTable('species', {
dog: {price: 5},
cat: {price: 4},
});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
console.log(relationships.getLocalRowIds('petSpecies', 'dog'));
// -> ['fido', 'cujo']
console.log(relationships.getLocalRowIds('petSpecies', 'worm'));
// -> []
console.log(relationships.getLocalRowIds('petColor', 'brown'));
// -> []
Since
v1.0.0
getRemoteRowId
The getRemoteRowId method gets the remote Row Id for a given local Row in a Relationship.
getRemoteRowId(
relationshipId: string,
localRowId: string,
): undefined | string| Type | Description | |
|---|---|---|
relationshipId | string | The |
localRowId | string | The |
| returns | undefined | string | The remote |
If the identified Relationship or Row does not exist (or if the definition references a Table that does not exist) then undefined is returned.
Example
This example creates a Store, creates a Relationships object, and defines a simple Relationship. It then uses getRemoteRowId to see the remote Row Id in the Relationship (and also the remote Row Ids for a local Row that does not exist, and for a Relationship that has not been defined).
import {createRelationships, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
})
.setTable('species', {
dog: {price: 5},
cat: {price: 4},
});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
console.log(relationships.getRemoteRowId('petSpecies', 'fido'));
// -> 'dog'
console.log(relationships.getRemoteRowId('petSpecies', 'toto'));
// -> undefined
console.log(relationships.getRemoteRowId('petColor', 'fido'));
// -> undefined
Since
v1.0.0
getRelationshipIds
The getRelationshipIds method returns an array of the Relationship Ids registered with this Relationships object.
getRelationshipIds(): IdsExample
This example creates a Relationships object with two definitions, and then gets the Ids of the definitions.
import {createRelationships, createStore} from 'tinybase';
const relationships = createRelationships(createStore())
.setRelationshipDefinition('petSpecies', 'pets', 'species', 'species')
.setRelationshipDefinition('petSequence', 'pets', 'pets', 'next');
console.log(relationships.getRelationshipIds());
// -> ['petSpecies', 'petSequence']
Since
v1.0.0
hasRelationship
The hasRelationship method returns a boolean indicating whether a given Relationship exists in the Relationships object.
hasRelationship(relationshipId: string): boolean| Type | Description | |
|---|---|---|
relationshipId | string | The |
| returns | boolean | Whether a |
Example
This example shows two simple Relationship existence checks.
import {createRelationships, createStore} from 'tinybase';
const relationships = createRelationships(
createStore(),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');
console.log(relationships.hasRelationship('petSpecies'));
// -> true
console.log(relationships.hasRelationship('petColor'));
// -> false
Since
v1.0.0
Listener methods
addLinkedRowIdsListener
The addLinkedRowIdsListener method registers a listener function with the Relationships object that will be called whenever the linked Row Ids in a linked list Relationship change.
addLinkedRowIdsListener(
relationshipId: string,
firstRowId: string,
listener: LinkedRowIdsListener,
): string| Type | Description | |
|---|---|---|
relationshipId | string | The |
firstRowId | string | |
listener | LinkedRowIdsListener | The function that will be called whenever the linked |
| returns | string | A unique |
A linked list Relationship is one that has the same Table specified as both local Table Id and remote Table Id, allowing you to create a sequence of Row objects within that one Table.
You listen to changes to a linked list starting from a single first Row by specifying the Relationship Id and local Row Id as the method's first two parameters.
Unlike other listener registration methods, you cannot provide null wildcards for the first two parameters of the addLinkedRowIdsListener method. This prevents the prohibitive expense of tracking all the possible linked lists (and partial linked lists within them) in a Store.
The provided listener is a LinkedRowIdsListener function, and will be called with a reference to the Relationships object, the Id of the Relationship, and the Id of the first Row that had its linked list change.
Example
This example creates a Store, a Relationships object, and then registers a listener that responds to any changes to a specific first Row's linked Row objects.
import {createRelationships, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', next: 'felix'},
felix: {species: 'cat', next: 'cujo'},
cujo: {species: 'dog'},
});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSequence',
'pets',
'pets',
'next',
);
const listenerId = relationships.addLinkedRowIdsListener(
'petSequence',
'fido',
(relationships) => {
console.log('petSequence linked list (from fido) changed');
console.log(relationships.getLinkedRowIds('petSequence', 'fido'));
},
);
store.setRow('pets', 'toto', {species: 'dog'});
store.setCell('pets', 'cujo', 'next', 'toto');
// -> 'petSequence linked list (from fido) changed'
// -> ['fido', 'felix', 'cujo', 'toto']
relationships.delListener(listenerId);
Since
v1.0.0
addLocalRowIdsListener
The addLocalRowIdsListener method registers a listener function with the Relationships object that will be called whenever the local Row Ids in a Relationship change.
addLocalRowIdsListener(
relationshipId: IdOrNull,
remoteRowId: IdOrNull,
listener: LocalRowIdsListener,
): string| Type | Description | |
|---|---|---|
relationshipId | IdOrNull | The |
remoteRowId | IdOrNull | The |
listener | LocalRowIdsListener | The function that will be called whenever the local |
| returns | string | A unique |
You can either listen to a single local Row (by specifying the Relationship Id and local Row Id as the method's first two parameters), or changes to any local Row (by providing a null wildcards).
Both, either, or neither of the relationshipId and remoteRowId parameters can be wildcarded with null. You can listen to a specific remote Row in a specific Relationship, any remote Row in a specific Relationship, a specific remote Row in any Relationship, or any remote Row in any Relationship.
The provided listener is a LocalRowIdsListener function, and will be called with a reference to the Relationships object, the Id of the Relationship, and the Id of the remote Row that had its local Row objects change.
Examples
This example creates a Store, a Relationships object, and then registers a listener that responds to any changes to a specific remote Row's local Row objects.
import {createRelationships, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
})
.setTable('species', {
wolf: {price: 10},
dog: {price: 5},
cat: {price: 4},
});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
const listenerId = relationships.addLocalRowIdsListener(
'petSpecies',
'dog',
(relationships) => {
console.log('petSpecies relationship (to dog) changed');
console.log(relationships.getLocalRowIds('petSpecies', 'dog'));
},
);
store.setRow('pets', 'toto', {species: 'dog'});
// -> 'petSpecies relationship (to dog) changed'
// -> ['fido', 'cujo', 'toto']
relationships.delListener(listenerId);
This example creates a Store, a Relationships object, and then registers a listener that responds to any changes to any remote Row's local Row objects.
import {createRelationships, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'brown'},
toto: {species: 'dog', color: 'grey'},
})
.setTable('species', {
wolf: {price: 10},
dog: {price: 5},
cat: {price: 4},
})
.setTable('color', {
brown: {discount: 0.1},
black: {discount: 0},
grey: {discount: 0.2},
});
const relationships = createRelationships(store)
.setRelationshipDefinition('petSpecies', 'pets', 'species', 'species')
.setRelationshipDefinition('petColor', 'pets', 'color', 'color');
const listenerId = relationships.addLocalRowIdsListener(
null,
null,
(relationships, relationshipId, remoteRowId) => {
console.log(
`${relationshipId} relationship (to ${remoteRowId}) changed`,
);
console.log(relationships.getLocalRowIds(relationshipId, remoteRowId));
},
);
store.setRow('pets', 'cujo', {species: 'wolf', color: 'grey'});
// -> 'petSpecies relationship (to dog) changed'
// -> ['fido', 'toto']
// -> 'petSpecies relationship (to wolf) changed'
// -> ['cujo']
// -> 'petColor relationship (to brown) changed'
// -> ['fido']
// -> 'petColor relationship (to grey) changed'
// -> ['toto', 'cujo']
relationships.delListener(listenerId);
Since
v1.0.0
addRemoteRowIdListener
The addRemoteRowIdListener method registers a listener function with the Relationships object that will be called whenever a remote Row Id in a Relationship changes.
addRemoteRowIdListener(
relationshipId: IdOrNull,
localRowId: IdOrNull,
listener: RemoteRowIdListener,
): string| Type | Description | |
|---|---|---|
relationshipId | IdOrNull | The |
localRowId | IdOrNull | The |
listener | RemoteRowIdListener | The function that will be called whenever the remote |
| returns | string | A unique |
You can either listen to a single local Row (by specifying the Relationship Id and local Row Id as the method's first two parameters), or changes to any local Row (by providing a null wildcards).
Both, either, or neither of the relationshipId and localRowId parameters can be wildcarded with null. You can listen to a specific local Row in a specific Relationship, any local Row in a specific Relationship, a specific local Row in any Relationship, or any local Row in any Relationship.
The provided listener is a RemoteRowIdListener function, and will be called with a reference to the Relationships object, the Id of the Relationship, and the Id of the local Row that had its remote Row change.
Examples
This example creates a Store, a Relationships object, and then registers a listener that responds to any changes to a specific local Row's remote Row.
import {createRelationships, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
})
.setTable('species', {
wolf: {price: 10},
dog: {price: 5},
cat: {price: 4},
});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
const listenerId = relationships.addRemoteRowIdListener(
'petSpecies',
'cujo',
(relationships) => {
console.log('petSpecies relationship (from cujo) changed');
console.log(relationships.getRemoteRowId('petSpecies', 'cujo'));
},
);
store.setCell('pets', 'cujo', 'species', 'wolf');
// -> 'petSpecies relationship (from cujo) changed'
// -> 'wolf'
relationships.delListener(listenerId);
This example creates a Store, a Relationships object, and then registers a listener that responds to any changes to any local Row's remote Row. It also illustrates how you can use the getStore method and the getRemoteRowId method to resolve the remote Row as a whole.
import {createRelationships, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'brown'},
})
.setTable('species', {
wolf: {price: 10},
dog: {price: 5},
cat: {price: 4},
})
.setTable('color', {
brown: {discount: 0.1},
black: {discount: 0},
grey: {discount: 0.2},
});
const relationships = createRelationships(store)
.setRelationshipDefinition('petSpecies', 'pets', 'species', 'species')
.setRelationshipDefinition('petColor', 'pets', 'color', 'color');
const listenerId = relationships.addRemoteRowIdListener(
null,
null,
(relationships, relationshipId, localRowId) => {
console.log(
`${relationshipId} relationship (from ${localRowId}) changed`,
);
console.log(relationships.getRemoteRowId(relationshipId, localRowId));
console.log(
relationships
.getStore()
.getRow(
relationships.getRemoteTableId(relationshipId),
relationships.getRemoteRowId(relationshipId, localRowId),
),
);
},
);
store.setRow('pets', 'cujo', {species: 'wolf', color: 'grey'});
// -> 'petSpecies relationship (from cujo) changed'
// -> 'wolf'
// -> {price: 10}
// -> 'petColor relationship (from cujo) changed'
// -> 'grey'
// -> {discount: 0.2}
relationships.delListener(listenerId);
Since
v1.0.0
addRelationshipIdsListener
The addRelationshipIdsListener method registers a listener function with the Relationships object that will be called whenever a Relationship definition is added or removed.
addRelationshipIdsListener(listener: RelationshipIdsListener): string| Type | Description | |
|---|---|---|
listener | RelationshipIdsListener | The function that will be called whenever a |
| returns | string |
The provided listener is a RelationshipIdsListener function, and will be called with a reference to the Relationships object.
Example
This example creates a Store, a Relationships object, and then registers a listener that responds to the addition and the removal of a Relationship definition.
import {createRelationships, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
})
.setTable('species', {
wolf: {price: 10},
dog: {price: 5},
cat: {price: 4},
});
const relationships = createRelationships(store);
const listenerId = relationships.addRelationshipIdsListener(
(relationships) => {
console.log(relationships.getRelationshipIds());
},
);
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
// -> ['petSpecies']
relationships.delRelationshipDefinition('petSpecies');
// -> []
relationships.delListener(listenerId);
Since
v4.1.0
delListener
The delListener method removes a listener that was previously added to the Relationships object.
delListener(listenerId: string): Relationships| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | Relationships | A reference to the |
Use the Id returned by whichever method was used to add the listener. Note that the Relationships object may re-use this Id for future listeners added to it.
Example
This example creates a Store, a Relationships object, registers a listener, and then removes it.
import {createRelationships, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
})
.setTable('species', {
wolf: {price: 10},
dog: {price: 5},
cat: {price: 4},
});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
const listenerId = relationships.addLocalRowIdsListener(
'petSpecies',
'dog',
() => {
console.log('petSpecies relationship (to dog) changed');
},
);
store.setRow('pets', 'toto', {species: 'dog'});
// -> 'petSpecies relationship (to dog) changed'
relationships.delListener(listenerId);
store.setRow('pets', 'toto', {species: 'dog'});
// -> undefined
// The listener is not called.
Since
v1.0.0
Configuration methods
delRelationshipDefinition
The delRelationshipDefinition method removes an existing Relationship definition.
delRelationshipDefinition(relationshipId: string): Relationships| Type | Description | |
|---|---|---|
relationshipId | string | The |
| returns | Relationships | A reference to the |
Example
This example creates a Store, creates a Relationships object, defines a simple Relationship, and then removes it.
import {createRelationships, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
})
.setTable('species', {
dog: {price: 5},
cat: {price: 4},
});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
console.log(relationships.getRelationshipIds());
// -> ['petSpecies']
relationships.delRelationshipDefinition('petSpecies');
console.log(relationships.getRelationshipIds());
// -> []
Since
v1.0.0
setRelationshipDefinition
The setRelationshipDefinition method lets you set the definition of a Relationship.
setRelationshipDefinition(
relationshipId: string,
localTableId: string,
remoteTableId: string,
getRemoteRowId: string | (getCell: GetCell, localRowId: string) => string,
): Relationships| Type | Description | |
|---|---|---|
relationshipId | string | The |
localTableId | string | The |
remoteTableId | string | The |
getRemoteRowId | string | (getCell: GetCell, localRowId: string) => string | Either the |
| returns | Relationships | A reference to the |
Every Relationship definition is identified by a unique Id, and if you re-use an existing Id with this method, the previous definition is overwritten.
An Relationship is based on connections between Row objects, often in two different Table objects. Therefore the definition requires the localTableId parameter to specify the 'local' Table to create the Relationship from, and the remoteTableId parameter to specify the 'remote' Table to create Relationship to.
A linked list Relationship is one that has the same Table specified as both local Table Id and remote Table Id, allowing you to create a sequence of Row objects within that one Table.
A local Row is related to a remote Row by specifying which of its (local) Cell values contains the (remote) Row Id, using the getRemoteRowId parameter. Alternatively, a custom function can be provided that produces your own remote Row Id from the local Row as a whole.
Examples
This example creates a Store, creates a Relationships object, and defines a simple Relationship based on the values in the species Cell of the pets Table that relates a Row to another in the species Table.
import {createRelationships, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
})
.setTable('species', {
dog: {price: 5},
cat: {price: 4},
});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSpecies', // relationshipId
'pets', // localTableId to link from
'species', // remoteTableId to link to
'species', // cellId containing remote key
);
console.log(relationships.getRemoteRowId('petSpecies', 'fido'));
// -> 'dog'
console.log(relationships.getLocalRowIds('petSpecies', 'dog'));
// -> ['fido', 'cujo']
This example creates a Store, creates a Relationships object, and defines a linked list Relationship based on the values in the next Cell of the pets Table that relates a Row to another in the same Table.
import {createRelationships, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', next: 'felix'},
felix: {species: 'cat', next: 'cujo'},
cujo: {species: 'dog'},
});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSequence', // relationshipId
'pets', // localTableId to link from
'pets', // the same remoteTableId to link within
'next', // cellId containing link key
);
console.log(relationships.getLinkedRowIds('petSequence', 'fido'));
// -> ['fido', 'felix', 'cujo']
Since
v1.0.0
Iterator methods
forEachRelationship
The forEachRelationship method takes a function that it will then call for each Relationship in a specified Relationships object.
forEachRelationship(relationshipCallback: RelationshipCallback): void| Type | Description | |
|---|---|---|
relationshipCallback | RelationshipCallback | The function that should be called for every |
| returns | void | This has no return value. |
This method is useful for iterating over the structure of the Relationships object in a functional style. The relationshipCallback parameter is a RelationshipCallback function that will be called with the Id of each Relationship, and with a function that can then be used to iterate over each local Row involved in the Relationship.
Example
This example iterates over each Relationship in a Relationships object, and lists each Row Id within them.
import {createRelationships, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', next: 'felix'},
felix: {species: 'cat', next: 'cujo'},
cujo: {species: 'dog'},
});
const relationships = createRelationships(store)
.setRelationshipDefinition('petSpecies', 'pets', 'species', 'species')
.setRelationshipDefinition('petSequence', 'pets', 'pets', 'next');
relationships.forEachRelationship((relationshipId, forEachRow) => {
console.log(relationshipId);
forEachRow((rowId) => console.log(`- ${rowId}`));
});
// -> 'petSpecies'
// -> '- fido'
// -> '- felix'
// -> '- cujo'
// -> 'petSequence'
// -> '- fido'
// -> '- felix'
// -> '- cujo'
Since
v1.0.0
Lifecycle methods
destroy
The destroy method should be called when this Relationships object is no longer used.
destroy(): voidThis guarantees that all of the listeners that the object registered with the underlying Store are removed and it can be correctly garbage collected.
Example
This example creates a Store, adds a Relationships object with a definition (that registers a RowListener with the underlying Store), and then destroys it again, removing the listener.
import {createRelationships, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
})
.setTable('species', {
wolf: {price: 10},
dog: {price: 5},
cat: {price: 4},
});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
console.log(store.getListenerStats().row);
// -> 1
relationships.destroy();
console.log(store.getListenerStats().row);
// -> 0
Since
v1.0.0
Development methods
getListenerStats
The getListenerStats method provides a set of statistics about the listeners registered with the Relationships object, and is used for debugging purposes.
getListenerStats(): RelationshipsListenerStats| returns | RelationshipsListenerStats | A |
|---|
The RelationshipsListenerStats object contains a breakdown of the different types of listener.
The method is intended to be used during development to ensure your application is not leaking listener registrations, for example.
Example
This example gets the listener statistics of a Relationships object.
import {createRelationships, createStore} from 'tinybase';
const store = createStore();
const relationships = createRelationships(store);
relationships.addRemoteRowIdListener(null, null, () => {
console.log('Remote Row Id changed');
});
relationships.addLocalRowIdsListener(null, null, () => {
console.log('Local Row Id changed');
});
const listenerStats = relationships.getListenerStats();
console.log(listenerStats.remoteRowId);
// -> 1
console.log(listenerStats.localRowIds);
// -> 1
Since
v1.0.0
Functions
createRelationships
The createRelationships function creates a Relationships object, and is the main entry point into the relationships module.
createRelationships(store: Store): Relationships| Type | Description | |
|---|---|---|
store | Store | The |
| returns | Relationships | A reference to the new |
A given Store can only have one Relationships object associated with it. If you call this function twice on the same Store, your second call will return a reference to the Relationships object created by the first.
Examples
This example creates a Relationships object.
import {createRelationships, createStore} from 'tinybase';
const store = createStore();
const relationships = createRelationships(store);
console.log(relationships.getRelationshipIds());
// -> []
This example creates a Relationships object, and calls the method a second time for the same Store to return the same object.
import {createRelationships, createStore} from 'tinybase';
const store = createStore();
const relationships1 = createRelationships(store);
const relationships2 = createRelationships(store);
console.log(relationships1 === relationships2);
// -> true
Since
v1.0.0
Type Aliases
Listener type aliases
LinkedRowIdsListener
The LinkedRowIdsListener type describes a function that is used to listen to changes to the local Row Id ends of a Relationship.
(
relationships: Relationships,
relationshipId: Id,
firstRowId: Id,
): void| Type | Description | |
|---|---|---|
relationships | Relationships | A reference to the |
relationshipId | Id | The |
firstRowId | Id | The |
| returns | void | This has no return value. |
A LinkedRowIdsListener is provided when using the addLinkedRowIdsListener method. See that method for specific examples.
When called, a LinkedRowIdsListener is given a reference to the Relationships object, the Id of the Relationship that changed, and the Id of the first Row of the linked list whose members changed.
Since
v1.0.0
LocalRowIdsListener
The LocalRowIdsListener type describes a function that is used to listen to changes to the local Row Id ends of a Relationship.
(
relationships: Relationships,
relationshipId: Id,
remoteRowId: Id,
): void| Type | Description | |
|---|---|---|
relationships | Relationships | A reference to the |
relationshipId | Id | The |
remoteRowId | Id | |
| returns | void | This has no return value. |
A LocalRowIdsListener is provided when using the addLocalRowIdsListener method. See that method for specific examples.
When called, a LocalRowIdsListener is given a reference to the Relationships object, the Id of the Relationship that changed, and the Id of the remote Row whose local Row Ids changed.
Since
v1.0.0
RemoteRowIdListener
The RemoteRowIdListener type describes a function that is used to listen to changes to the remote Row Id end of a Relationship.
(
relationships: Relationships,
relationshipId: Id,
localRowId: Id,
): void| Type | Description | |
|---|---|---|
relationships | Relationships | A reference to the |
relationshipId | Id | The |
localRowId | Id | |
| returns | void | This has no return value. |
A RemoteRowIdListener is provided when using the addRemoteRowIdListener method. See that method for specific examples.
When called, a RemoteRowIdListener is given a reference to the Relationships object, the Id of the Relationship that changed, and the Id of the local Row whose remote Row Id changed.
Since
v1.0.0
RelationshipIdsListener
The RelationshipIdsListener type describes a function that is used to listen to Relationship definitions being added or removed.
(relationships: Relationships): void| Type | Description | |
|---|---|---|
relationships | Relationships | A reference to the |
| returns | void | This has no return value. |
A RelationshipIdsListener is provided when using the addRelationshipIdsListener method. See that method for specific examples.
When called, a RelationshipIdsListener is given a reference to the Relationships object.
Since
v1.0.0
Callback type aliases
RelationshipCallback
The RelationshipCallback type describes a function that takes a Relationship's Id and a callback to loop over each local Row within it.
(
relationshipId: Id,
forEachRow: (rowCallback: RowCallback) => void,
): void| Type | Description | |
|---|---|---|
relationshipId | Id | The |
forEachRow | (rowCallback: RowCallback) => void | A function that will let you iterate over the local |
| returns | void | This has no return value. |
A RelationshipCallback is provided when using the forEachRelationship method, so that you can do something based on every Relationship in the Relationships object. See that method for specific examples.
Since
v1.0.0
Concept type aliases
Relationship
The Relationship type represents the concept of a map that connects one Row object to another, often in another Table.
{
remoteRowId: {[localRowId: Id]: Id};
localRowIds: {[remoteRowId: Id]: Ids};
linkedRowIds: {[firstRowId: Id]: Ids};
}| Type | Description | |
|---|---|---|
remoteRowId | {[localRowId: Id]: Id} | |
localRowIds | {[remoteRowId: Id]: Ids} | |
linkedRowIds | {[firstRowId: Id]: Ids} |
The Relationship has a one-to-many nature. One local Row Id is linked to one remote Row Id (in the remote Table), as described by the setRelationshipDefinition method - and one remote Row Id may map back to multiple local Row Ids (in the local Table).
A Relationship where the local Table is the same as the remote Table can be used to model a 'linked list', where Row A references Row B, Row B references Row C, and so on.
Note that the Relationship type is not actually used in the API, and you instead enumerate and access its structure with the getRemoteRowId method, the getLocalRowIds method, and the getLinkedRowIds method.
Since
v1.0.0
Development type aliases
RelationshipsListenerStats
The RelationshipsListenerStats type describes the number of listeners registered with the Relationships object, and can be used for debugging purposes.
{
remoteRowId: number;
localRowIds: number;
linkedRowIds: number;
}| Type | Description | |
|---|---|---|
remoteRowId | number | The number of |
localRowIds | number | The number of |
linkedRowIds | number | The number of LinkedRowId functions registered with the |
A RelationshipsListenerStats object is returned from the getListenerStats method.
Since
v1.0.0
queries
The queries module of the TinyBase project provides the ability to create and track queries of the data in Store objects.
The main entry point to using the queries module is the createQueries function, which returns a new Queries object. That object in turn has methods that let you create new query definitions, access their results directly, and register listeners for when those results change.
Since
v2.0.0
Interfaces
Queries
A Queries object lets you create and track queries of the data in Store objects.
This is useful for creating a reactive view of data that is stored in physical tables: selecting columns, joining tables together, filtering rows, aggregating data, sorting it, and so on.
This provides a generalized query concept for Store data. If you just want to create and track metrics, indexes, or relationships between rows, you may prefer to use the dedicated Metrics, Indexes, and Relationships objects, which have simpler APIs.
Create a Queries object easily with the createQueries function. From there, you can add new query definitions (with the setQueryDefinition method), query the results (with the getResultTable method, the getResultRow method, the getResultCell method, and so on), and add listeners for when they change (with the addResultTableListener method, the addResultRowListener method, the addResultCellListener method, and so on).
Example
This example shows a very simple lifecycle of a Queries object: from creation, to adding definitions, getting their contents, and then registering and removing listeners for them.
import {createQueries, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', color: 'brown', ownerId: '1'},
felix: {species: 'cat', color: 'black', ownerId: '2'},
cujo: {species: 'dog', color: 'black', ownerId: '3'},
})
.setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
})
.setTable('owners', {
'1': {name: 'Alice'},
'2': {name: 'Bob'},
'3': {name: 'Carol'},
});
const queries = createQueries(store);
// A filtered table query:
queries.setQueryDefinition('blackPets', 'pets', ({select, where}) => {
select('species');
where('color', 'black');
});
console.log(queries.getResultTable('blackPets'));
// -> {felix: {species: 'cat'}, cujo: {species: 'dog'}}
// A joined table query:
queries.setQueryDefinition('petOwners', 'pets', ({select, join}) => {
select('owners', 'name').as('owner');
join('owners', 'ownerId');
});
console.log(queries.getResultTable('petOwners'));
// -> {fido: {owner: 'Alice'}, felix: {owner: 'Bob'}, cujo: {owner: 'Carol'}}
// A grouped query:
queries.setQueryDefinition(
'colorPrice',
'pets',
({select, join, group}) => {
select('color');
select('species', 'price');
join('species', 'species');
group('price', 'avg');
},
);
console.log(queries.getResultTable('colorPrice'));
// -> {"1": {color: 'black', price: 4.5}, "0": {color: 'brown', price: 5}}
console.log(queries.getResultSortedRowIds('colorPrice', 'price', true));
// -> ["0", "1"]
const listenerId = queries.addResultTableListener('colorPrice', () => {
console.log('Average prices per color changed');
console.log(queries.getResultTable('colorPrice'));
console.log(queries.getResultSortedRowIds('colorPrice', 'price', true));
});
store.setRow('pets', 'lowly', {species: 'worm', color: 'brown'});
// -> 'Average prices per color changed'
// -> {"0": {color: 'brown', price: 3}, "1": {color: 'black', price: 4.5}}
// -> ["1", "0"]
queries.delListener(listenerId);
queries.destroy();
See also
- Using Queries guides
- Car Analysis demo
- Movie Database demo
Since
v2.0.0
Getter methods
getStore
The getStore method returns a reference to the underlying Store that is backing this Queries object.
getStore(): StoreExample
This example creates a Queries object against a newly-created Store and then gets its reference in order to update its data.
import {createQueries, createStore} from 'tinybase';
const queries = createQueries(createStore());
queries.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
queries
.getStore()
.setRow('pets', 'fido', {species: 'dog', color: 'brown'});
console.log(queries.getResultTable('dogColors'));
// -> {fido: {color: 'brown'}}
Since
v2.0.0
getTableId
The getTableId method returns the Id of the underlying Table that is backing a query.
getTableId(queryId: string): undefined | string| Type | Description | |
|---|---|---|
queryId | string | The |
| returns | undefined | string |
If the query Id is invalid, the method returns undefined.
Example
This example creates a Queries object, a single query definition, and then calls this method on it (as well as a non-existent definition) to get the underlying Table Id.
import {createQueries, createStore} from 'tinybase';
const queries = createQueries(createStore()).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.getTableId('dogColors'));
// -> 'pets'
console.log(queries.getTableId('catColors'));
// -> undefined
Since
v2.0.0
getQueryIds
The getQueryIds method returns an array of the query Ids registered with this Queries object.
getQueryIds(): IdsExample
This example creates a Queries object with two definitions, and then gets the Ids of the definitions.
import {createQueries, createStore} from 'tinybase';
const queries = createQueries(createStore())
.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
})
.setQueryDefinition('catColors', 'pets', ({select, where}) => {
select('color');
where('species', 'cat');
});
console.log(queries.getQueryIds());
// -> ['dogColors', 'catColors']
Since
v2.0.0
hasQuery
The hasQuery method returns a boolean indicating whether a given query exists in the Queries object.
hasQuery(queryId: string): boolean| Type | Description | |
|---|---|---|
queryId | string | |
| returns | boolean | Whether a query with that |
Example
This example shows two simple query existence checks.
import {createQueries, createStore} from 'tinybase';
const queries = createQueries(createStore()).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.hasQuery('dogColors'));
// -> true
console.log(queries.hasQuery('catColors'));
// -> false
Since
v2.0.0
Result methods
getResultTable
The getResultTable method returns an object containing the entire data of the ResultTable of the given query.
getResultTable(queryId: string): ResultTable| Type | Description | |
|---|---|---|
queryId | string | The |
| returns | ResultTable | An object containing the entire data of the |
This has the same behavior as a Store's getTable method. For example, if the query Id is invalid, the method returns an empty object. Similarly, it returns a copy of, rather than a reference to the underlying data, so changes made to the returned object are not made to the query results themselves.
Example
This example creates a Queries object, a single query definition, and then calls this method on it (as well as a non-existent definition) to get the ResultTable.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.getResultTable('dogColors'));
// -> {fido: {color: 'brown'}, cujo: {color: 'black'}}
console.log(queries.getResultTable('catColors'));
// -> {}
Since
v2.0.0
getResultTableCellIds
The getResultTableCellIds method returns the Ids of every ResultCell used across the ResultTable of the given query.
getResultTableCellIds(queryId: string): Ids| Type | Description | |
|---|---|---|
queryId | string | The |
| returns | Ids | An array of the |
This has the same behavior as a Store's getTableCellIds method. For example, if the query Id is invalid, the method returns an empty array. Similarly, it returns a copy of, rather than a reference to the list of Ids, so changes made to the list object are not made to the query results themselves.
Example
This example creates a Queries object, a single query definition, and then calls this method on it (as well as a non-existent definition) to get the ResultCell Ids.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black', legs: 4},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
select('legs');
where('species', 'dog');
},
);
console.log(queries.getResultTableCellIds('dogColors'));
// -> ['color', 'legs']
console.log(queries.getResultTableCellIds('catColors'));
// -> []
Since
v4.1.0
hasResultTable
The hasResultTable method returns a boolean indicating whether a given ResultTable exists.
hasResultTable(queryId: string): boolean| Type | Description | |
|---|---|---|
queryId | string | The |
| returns | boolean | Whether a |
Example
This example shows two simple ResultTable existence checks.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.hasResultTable('dogColors'));
// -> true
console.log(queries.hasResultTable('catColors'));
// -> false
Since
v2.0.0
getResultRowIds
The getResultRowIds method returns the Ids of every ResultRow in the ResultTable of the given query.
getResultRowIds(queryId: string): Ids| Type | Description | |
|---|---|---|
queryId | string | The |
| returns | Ids | An array of the |
This has the same behavior as a Store's getRowIds method. For example, if the query Id is invalid, the method returns an empty array. Similarly, it returns a copy of, rather than a reference to the list of Ids, so changes made to the list object are not made to the query results themselves.
Example
This example creates a Queries object, a single query definition, and then calls this method on it (as well as a non-existent definition) to get the ResultRow Ids.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.getResultRowIds('dogColors'));
// -> ['fido', 'cujo']
console.log(queries.getResultRowIds('catColors'));
// -> []
Since
v2.0.0
getResultSortedRowIds
The getResultSortedRowIds method returns the Ids of every ResultRow in the ResultTable of the given query, sorted according to the values in a specified ResultCell.
getResultSortedRowIds(
queryId: string,
cellId?: string,
descending?: boolean,
offset?: number,
limit?: number,
): Ids| Type | Description | |
|---|---|---|
queryId | string | The |
cellId? | string | The |
descending? | boolean | Whether the sorting should be in descending order. |
offset? | number | The number of |
limit? | number | The maximum number of |
| returns | Ids | An array of the sorted |
This has the same behavior as a Store's getSortedRowIds method. For example, if the query Id is invalid, the method returns an empty array. Similarly, the sorting of the rows is alphanumeric, and you can indicate whether it should be in descending order. The offset and limit parameters are used to paginate results, but default to 0 and undefined to return all available ResultRow Ids if not specified.
Note that every call to this method will perform the sorting afresh - there is no caching of the results - and so you are advised to memoize the results yourself, especially when the ResultTable is large. For a performant approach to tracking the sorted ResultRow Ids when they change, use the addResultSortedRowIdsListener method.
Example
This example creates a Queries object, a single query definition, and then calls this method on it (as well as a non-existent definition) to get the ResultRow Ids.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.getResultSortedRowIds('dogColors', 'color'));
// -> ['cujo', 'fido']
console.log(queries.getResultSortedRowIds('catColors', 'color'));
// -> []
Since
v2.0.0
getResultRow
The getResultRow method returns an object containing the entire data of a single ResultRow in the ResultTable of the given query.
getResultRow(
queryId: string,
rowId: string,
): ResultRow| Type | Description | |
|---|---|---|
queryId | string | The |
rowId | string | The |
| returns | ResultRow | An object containing the entire data of the |
This has the same behavior as a Store's getRow method. For example, if the query or ResultRow Id is invalid, the method returns an empty object. Similarly, it returns a copy of, rather than a reference to the underlying data, so changes made to the returned object are not made to the query results themselves.
Example
This example creates a Queries object, a single query definition, and then calls this method on it (as well as a non-existent ResultRow Id) to get the ResultRow.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.getResultRow('dogColors', 'fido'));
// -> {color: 'brown'}
console.log(queries.getResultRow('dogColors', 'felix'));
// -> {}
Since
v2.0.0
getResultRowCount
The getResultRowCount method returns the count of the ResultRow objects in the ResultTable of the given query.
getResultRowCount(queryId: string): number| Type | Description | |
|---|---|---|
queryId | string | The |
| returns | number | The number of |
This has the same behavior as a Store's getRowCount method. For example, if the query Id is invalid, the method returns zero.
Example
This example creates a Queries object, a single query definition, and then calls this method on it (as well as a non-existent definition) to get the ResultRow count.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.getResultRowCount('dogColors'));
// -> 2
console.log(queries.getResultRowCount('catColors'));
// -> 0
Since
v4.1.0
hasResultRow
The hasResultRow method returns a boolean indicating whether a given ResultRow exists.
hasResultRow(
queryId: string,
rowId: string,
): boolean| Type | Description | |
|---|---|---|
queryId | string | The |
rowId | string | |
| returns | boolean |
Example
This example shows two simple ResultRow existence checks.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.hasResultRow('dogColors', 'fido'));
// -> true
console.log(queries.hasResultRow('dogColors', 'felix'));
// -> false
Since
v2.0.0
getResultCellIds
The getResultCellIds method returns the Ids of every ResultCell in a given ResultRow, in the ResultTable of the given query.
getResultCellIds(
queryId: string,
rowId: string,
): Ids| Type | Description | |
|---|---|---|
queryId | string | The |
rowId | string | The |
| returns | Ids | An array of the |
This has the same behavior as a Store's getCellIds method. For example, if the query Id or ResultRow Id is invalid, the method returns an empty array. Similarly, it returns a copy of, rather than a reference to the list of Ids, so changes made to the list object are not made to the query results themselves.
Example
This example creates a Queries object, a single query definition, and then calls this method on it (as well as a non-existent ResultRow Id) to get the ResultCell Ids.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.getResultCellIds('dogColors', 'fido'));
// -> ['color']
console.log(queries.getResultCellIds('dogColors', 'felix'));
// -> []
Since
v2.0.0
getResultCell
The getResultCell method returns the value of a single ResultCell in a given ResultRow, in the ResultTable of the given query.
getResultCell(
queryId: string,
rowId: string,
cellId: string,
): ResultCellOrUndefined| Type | Description | |
|---|---|---|
queryId | string | The |
rowId | string | The |
cellId | string | The |
| returns | ResultCellOrUndefined | The value of the |
This has the same behavior as a Store's getCell method. For example, if the query, or ResultRow, or ResultCell Id is invalid, the method returns undefined.
Example
This example creates a Queries object, a single query definition, and then calls this method on it (as well as a non-existent ResultCell Id) to get the ResultCell.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.getResultCell('dogColors', 'fido', 'color'));
// -> 'brown'
console.log(queries.getResultCell('dogColors', 'fido', 'species'));
// -> undefined
Since
v2.0.0
hasResultCell
The hasResultCell method returns a boolean indicating whether a given ResultCell exists.
hasResultCell(
queryId: string,
rowId: string,
cellId: string,
): boolean| Type | Description | |
|---|---|---|
queryId | string | The |
rowId | string | |
cellId | string | The |
| returns | boolean | Whether a |
Example
This example shows two simple ResultRow existence checks.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.hasResultCell('dogColors', 'fido', 'color'));
// -> true
console.log(queries.hasResultCell('dogColors', 'fido', 'species'));
// -> false
Since
v2.0.0
Listener methods
addResultTableCellIdsListener
The addResultTableCellIdsListener method registers a listener function with the Queries object that will be called whenever the Cell Ids that appear anywhere in a ResultTable change.
addResultTableCellIdsListener(
queryId: IdOrNull,
listener: ResultTableCellIdsListener,
): string| Type | Description | |
|---|---|---|
queryId | IdOrNull | The |
listener | ResultTableCellIdsListener | The function that will be called whenever the |
| returns | string | A unique |
The provided listener is a ResultTableCellIdsListener function, and will be called with a reference to the Queries object and the Id of the ResultTable that changed (which is also the query Id).
By default, such a listener is only called when a Cell Id is added to, or removed from, the ResultTable. To listen to all changes in the ResultTable, use the addResultTableListener method.
You can either listen to a single ResultTable (by specifying a query Id as the method's first parameter) or changes to any ResultTable (by providing a null wildcard).
Examples
This example registers a listener that responds to any change to the Cell Ids of a specific ResultTable.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColorsAndLegs',
'pets',
({select, where}) => {
select('color');
select('legs');
where('species', 'dog');
},
);
const listenerId = queries.addResultTableCellIdsListener(
'dogColorsAndLegs',
(queries) => {
console.log(`Cell Ids for dogColorsAndLegs result table changed`);
console.log(queries.getResultTableCellIds('dogColorsAndLegs'));
},
);
store.setCell('pets', 'cujo', 'legs', 4);
// -> 'Cell Ids for dogColorsAndLegs result table changed'
// -> ['color', 'legs']
store.delListener(listenerId);
This example registers a listener that responds to any change to the ResultCell Ids of any ResultTable.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black', legs: 4},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store)
.setQueryDefinition('dogColorsAndLegs', 'pets', ({select, where}) => {
select('color');
select('legs');
where('species', 'dog');
})
.setQueryDefinition('catColorsAndLegs', 'pets', ({select, where}) => {
select('color');
select('legs');
where('species', 'cat');
});
const listenerId = queries.addResultTableCellIdsListener(
null,
(queries, tableId) => {
console.log(`Cell Ids for ${tableId} result table changed`);
},
);
store.setCell('pets', 'cujo', 'legs', 4);
// -> 'Cell Ids for dogColorsAndLegs result table changed'
store.delCell('pets', 'felix', 'legs');
// -> 'Cell Ids for catColorsAndLegs result table changed'
store.delListener(listenerId);
Since
v2.0.0
addResultTableListener
The addResultTableListener method registers a listener function with the Queries object that will be called whenever data in a ResultTable changes.
addResultTableListener(
queryId: IdOrNull,
listener: ResultTableListener,
): string| Type | Description | |
|---|---|---|
queryId | IdOrNull | The |
listener | ResultTableListener | The function that will be called whenever data in the matching |
| returns | string | A unique |
The provided listener is a ResultTableListener function, and will be called with a reference to the Queries object, the Id of the ResultTable that changed (which is also the query Id), and a GetResultCellChange function in case you need to inspect any changes that occurred.
You can either listen to a single ResultTable (by specifying a query Id as the method's first parameter) or changes to any ResultTable (by providing a null wildcard).
Examples
This example registers a listener that responds to any changes to a specific ResultTable.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const listenerId = queries.addResultTableListener(
'dogColors',
(queries, tableId, getCellChange) => {
console.log('dogColors result table changed');
console.log(getCellChange('dogColors', 'fido', 'color'));
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'dogColors result table changed'
// -> [true, 'brown', 'walnut']
store.delListener(listenerId);
This example registers a listener that responds to any changes to any ResultTable.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store)
.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
})
.setQueryDefinition('catColors', 'pets', ({select, where}) => {
select('color');
where('species', 'cat');
});
const listenerId = queries.addResultTableListener(
null,
(queries, tableId) => {
console.log(`${tableId} result table changed`);
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'dogColors result table changed'
store.setCell('pets', 'felix', 'color', 'tortoiseshell');
// -> 'catColors result table changed'
store.delListener(listenerId);
Since
v2.0.0
addResultRowIdsListener
The addResultRowIdsListener method registers a listener function with the Queries object that will be called whenever the ResultRow Ids in a ResultTable change.
addResultRowIdsListener(
queryId: IdOrNull,
listener: ResultRowIdsListener,
): string| Type | Description | |
|---|---|---|
queryId | IdOrNull | The |
listener | ResultRowIdsListener | The function that will be called whenever the |
| returns | string | A unique |
The provided listener is a ResultRowIdsListener function, and will be called with a reference to the Queries object and the Id of the ResultTable that changed (which is also the query Id).
By default, such a listener is only called when a ResultRow is added to, or removed from, the ResultTable. To listen to all changes in the ResultTable, use the addResultTableListener method.
You can either listen to a single ResultTable (by specifying a query Id as the method's first parameter) or changes to any ResultTable (by providing a null wildcard).
Examples
This example registers a listener that responds to any change to the ResultRow Ids of a specific ResultTable.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const listenerId = queries.addResultRowIdsListener(
'dogColors',
(queries) => {
console.log(`Row Ids for dogColors result table changed`);
console.log(queries.getResultRowIds('dogColors'));
},
);
store.setRow('pets', 'rex', {species: 'dog', color: 'tan'});
// -> 'Row Ids for dogColors result table changed'
// -> ['fido', 'cujo', 'rex']
store.delListener(listenerId);
This example registers a listener that responds to any change to the ResultRow Ids of any ResultTable.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store)
.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
})
.setQueryDefinition('catColors', 'pets', ({select, where}) => {
select('color');
where('species', 'cat');
});
const listenerId = queries.addResultRowIdsListener(
null,
(queries, tableId) => {
console.log(`Row Ids for ${tableId} result table changed`);
},
);
store.setRow('pets', 'rex', {species: 'dog', color: 'tan'});
// -> 'Row Ids for dogColors result table changed'
store.setRow('pets', 'tom', {species: 'cat', color: 'gray'});
// -> 'Row Ids for catColors result table changed'
store.delListener(listenerId);
Since
v2.0.0
addResultSortedRowIdsListener
The addResultSortedRowIdsListener method registers a listener function with the Queries object that will be called whenever sorted (and optionally, paginated) ResultRow Ids in a ResultTable change.
addResultSortedRowIdsListener(
queryId: string,
cellId: undefined | string,
descending: boolean,
offset: number,
limit: undefined | number,
listener: ResultSortedRowIdsListener,
): string| Type | Description | |
|---|---|---|
queryId | string | The |
cellId | undefined | string | The |
descending | boolean | Whether the sorting should be in descending order. |
offset | number | The number of |
limit | undefined | number | The maximum number of |
listener | ResultSortedRowIdsListener | The function that will be called whenever the sorted |
| returns | string | A unique |
The provided listener is a ResultSortedRowIdsListener function, and will be called with a reference to the Queries object, the Id of the ResultTable whose ResultRow Ids sorting changed (which is also the query Id), the ResultCell Id being used to sort them, whether descending or not, and the offset and limit of the number of Ids returned, for pagination purposes. It also receives the sorted array of Ids itself, so that you can use them in the listener without the additional cost of an explicit call to the getResultSortedRowIds method.
Such a listener is called when a ResultRow is added or removed, but also when a value in the specified ResultCell (somewhere in the ResultTable) has changed enough to change the sorting of the ResultRow Ids.
Unlike most other listeners, you cannot provide wildcards (due to the cost of detecting changes to the sorting). You can only listen to a single specified ResultTable, sorted by a single specified ResultCell.
The sorting of the rows is alphanumeric, and you can indicate whether it should be in descending order. The offset and limit parameters are used to paginate results, but default to 0 and undefined to return all available ResultRow Ids if not specified.
Examples
This example registers a listener that responds to any change to the sorted ResultRow Ids of a specific ResultTable.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const listenerId = queries.addResultSortedRowIdsListener(
'dogColors',
'color',
false,
0,
undefined,
(queries, tableId, cellId, descending, offset, limit, sortedRowIds) => {
console.log(`Sorted row Ids for dogColors result table changed`);
console.log(sortedRowIds);
// ^ cheaper than calling getResultSortedRowIds again
},
);
store.setRow('pets', 'rex', {species: 'dog', color: 'tan'});
// -> 'Sorted row Ids for dogColors result table changed'
// -> ['cujo', 'fido', 'rex']
store.delListener(listenerId);
This example registers a listener that responds to any change to the sorted ResultRow Ids of a specific ResultTable. The ResultRow Ids are sorted by their own value, since the cellId parameter is explicitly undefined.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.getResultSortedRowIds('dogColors', undefined));
// -> ['cujo', 'fido']
const listenerId = queries.addResultSortedRowIdsListener(
'dogColors',
undefined,
false,
0,
undefined,
(queries, tableId, cellId, descending, offset, limit, sortedRowIds) => {
console.log(`Sorted row Ids for dogColors result table changed`);
console.log(sortedRowIds);
// ^ cheaper than calling getSortedRowIds again
},
);
store.setRow('pets', 'rex', {species: 'dog', color: 'tan'});
// -> 'Sorted row Ids for dogColors result table changed'
// -> ['cujo', 'fido', 'rex']
store.delListener(listenerId);
Since
v2.0.0
addResultRowCountListener
The addResultRowCountListener method registers a listener function with the Queries object that will be called whenever the count of ResultRow objects in a ResultTable changes.
addResultRowCountListener(
queryId: IdOrNull,
listener: ResultRowCountListener,
): string| Type | Description | |
|---|---|---|
queryId | IdOrNull | The |
listener | ResultRowCountListener | The function that will be called whenever the number of |
| returns | string | A unique |
The provided listener is a ResultRowCountListener function, and will be called with a reference to the Queries object, the Id of the ResultTable that changed (which is also the query Id), and the number of ResultRow objects in th ResultTable.
You can either listen to a single ResultTable (by specifying a query Id as the method's first parameter) or changes to any ResultTable (by providing a null wildcard).
Examples
This example registers a listener that responds to a change in the number of ResultRow objects in a specific ResultTable.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const listenerId = queries.addResultRowCountListener(
'dogColors',
(queries, tableId, count) => {
console.log(
'Row count for dogColors result table changed to ' + count,
);
},
);
store.setRow('pets', 'rex', {species: 'dog', color: 'tan'});
// -> 'Row count for dogColors result table changed to 3'
store.delListener(listenerId);
This example registers a listener that responds to a change in the number of ResultRow objects any ResultTable.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store)
.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
})
.setQueryDefinition('catColors', 'pets', ({select, where}) => {
select('color');
where('species', 'cat');
});
const listenerId = queries.addResultRowCountListener(
null,
(queries, tableId, count) => {
console.log(
`Row count for ${tableId} result table changed to ${count}`,
);
},
);
store.setRow('pets', 'rex', {species: 'dog', color: 'tan'});
// -> 'Row count for dogColors result table changed to 3'
store.setRow('pets', 'tom', {species: 'cat', color: 'gray'});
// -> 'Row count for catColors result table changed to 2'
store.delListener(listenerId);
Since
v4.1.0
addResultRowListener
The addResultRowListener method registers a listener function with the Queries object that will be called whenever data in a ResultRow changes.
addResultRowListener(
queryId: IdOrNull,
rowId: IdOrNull,
listener: ResultRowListener,
): string| Type | Description | |
|---|---|---|
queryId | IdOrNull | The |
rowId | IdOrNull | The |
listener | ResultRowListener | The function that will be called whenever data in the matching |
| returns | string | A unique |
The provided listener is a ResultRowListener function, and will be called with a reference to the Queries object, the Id of the ResultTable that changed (which is also the query Id), and a GetResultCellChange function in case you need to inspect any changes that occurred.
You can either listen to a single ResultRow (by specifying a query Id and ResultRow Id as the method's first two parameters) or changes to any ResultRow (by providing null wildcards).
Both, either, or neither of the queryId and rowId parameters can be wildcarded with null. You can listen to a specific ResultRow in a specific query, any ResultRow in a specific query, a specific ResultRow in any query, or any ResultRow in any query.
Examples
This example registers a listener that responds to any changes to a specific ResultRow.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const listenerId = queries.addResultRowListener(
'dogColors',
'fido',
(queries, tableId, rowId, getCellChange) => {
console.log('fido row in dogColors result table changed');
console.log(getCellChange('dogColors', 'fido', 'color'));
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'fido row in dogColors result table changed'
// -> [true, 'brown', 'walnut']
store.delListener(listenerId);
This example registers a listener that responds to any changes to any ResultRow.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store)
.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
})
.setQueryDefinition('catColors', 'pets', ({select, where}) => {
select('color');
where('species', 'cat');
});
const listenerId = queries.addResultRowListener(
null,
null,
(queries, tableId, rowId) => {
console.log(`${rowId} row in ${tableId} result table changed`);
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'fido row in dogColors result table changed'
store.setCell('pets', 'felix', 'color', 'tortoiseshell');
// -> 'felix row in catColors result table changed'
store.delListener(listenerId);
Since
v2.0.0
addResultCellIdsListener
The addResultCellIdsListener method registers a listener function with the Queries object that will be called whenever the ResultCell Ids in a ResultRow change.
addResultCellIdsListener(
queryId: IdOrNull,
rowId: IdOrNull,
listener: ResultCellIdsListener,
): string| Type | Description | |
|---|---|---|
queryId | IdOrNull | The |
rowId | IdOrNull | The |
listener | ResultCellIdsListener | The function that will be called whenever the |
| returns | string | A unique |
The provided listener is a ResultCellIdsListener function, and will be called with a reference to the Queries object, the Id of the ResultTable (which is also the query Id), and the Id of the ResultRow that changed.
Such a listener is only called when a ResultCell is added to, or removed from, the ResultRow. To listen to all changes in the ResultRow, use the addResultRowListener method.
You can either listen to a single ResultRow (by specifying the query Id and ResultRow Id as the method's first two parameters) or changes to any ResultRow (by providing null wildcards).
Both, either, or neither of the queryId and rowId parameters can be wildcarded with null. You can listen to a specific ResultRow in a specific query, any ResultRow in a specific query, a specific ResultRow in any query, or any ResultRow in any query.
Examples
This example registers a listener that responds to any change to the ResultCell Ids of a specific ResultRow.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
select('price');
where('species', 'dog');
},
);
const listenerId = queries.addResultCellIdsListener(
'dogColors',
'fido',
(queries) => {
console.log(`Cell Ids for fido row in dogColors result table changed`);
console.log(queries.getResultCellIds('dogColors', 'fido'));
},
);
store.setCell('pets', 'fido', 'price', 5);
// -> 'Cell Ids for fido row in dogColors result table changed'
// -> ['color', 'price']
store.delListener(listenerId);
This example registers a listener that responds to any change to the ResultCell Ids of any ResultRow.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store)
.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
select('price');
where('species', 'dog');
})
.setQueryDefinition('catColors', 'pets', ({select, where}) => {
select('color');
select('purrs');
where('species', 'cat');
});
const listenerId = queries.addResultCellIdsListener(
null,
null,
(queries, tableId, rowId) => {
console.log(
`Cell Ids for ${rowId} row in ${tableId} result table changed`,
);
},
);
store.setCell('pets', 'fido', 'price', 5);
// -> 'Cell Ids for fido row in dogColors result table changed'
store.setCell('pets', 'felix', 'purrs', true);
// -> 'Cell Ids for felix row in catColors result table changed'
store.delListener(listenerId);
Since
v2.0.0
addResultCellListener
The addResultCellListener method registers a listener function with the Queries object that will be called whenever data in a ResultCell changes.
addResultCellListener(
queryId: IdOrNull,
rowId: IdOrNull,
cellId: IdOrNull,
listener: ResultCellListener,
): string| Type | Description | |
|---|---|---|
queryId | IdOrNull | The |
rowId | IdOrNull | The |
cellId | IdOrNull | The |
listener | ResultCellListener | The function that will be called whenever data in the matching |
| returns | string | A unique |
The provided listener is a ResultCellListener function, and will be called with a reference to the Queries object, the Id of the ResultTable that changed (which is also the query Id), the Id of the ResultRow that changed, the Id of the ResultCell that changed, the new ResultCell value, the old ResultCell value, and a GetResultCellChange function in case you need to inspect any changes that occurred.
You can either listen to a single ResultRow (by specifying a query Id, ResultRow Id, and ResultCell Id as the method's first three parameters) or changes to any ResultCell (by providing null wildcards).
All, some, or none of the queryId, rowId, and cellId parameters can be wildcarded with null. You can listen to a specific ResultCell in a specific ResultRow in a specific query, any ResultCell in any ResultRow in any query, for example - or every other combination of wildcards.
Examples
This example registers a listener that responds to any changes to a specific ResultCell.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const listenerId = queries.addResultCellListener(
'dogColors',
'fido',
'color',
(queries, tableId, rowId, cellId, newCell, oldCell, getCellChange) => {
console.log(
'color cell in fido row in dogColors result table changed',
);
console.log([oldCell, newCell]);
console.log(getCellChange('dogColors', 'fido', 'color'));
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'color cell in fido row in dogColors result table changed'
// -> ['brown', 'walnut']
// -> [true, 'brown', 'walnut']
store.delListener(listenerId);
This example registers a listener that responds to any changes to any ResultCell.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown', price: 5},
felix: {species: 'cat', color: 'black', price: 4},
cujo: {species: 'dog', color: 'black', price: 5},
});
const queries = createQueries(store)
.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
})
.setQueryDefinition('catColors', 'pets', ({select, where}) => {
select('color');
select('price');
where('species', 'cat');
});
const listenerId = queries.addResultCellListener(
null,
null,
null,
(queries, tableId, rowId, cellId) => {
console.log(
`${cellId} cell in ${rowId} row in ${tableId} result table changed`,
);
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'color cell in fido row in dogColors result table changed'
store.setCell('pets', 'felix', 'price', 3);
// -> 'price cell in felix row in catColors result table changed'
store.delListener(listenerId);
Since
v2.0.0
addQueryIdsListener
The addQueryIdsListener method registers a listener function with the Queries object that will be called whenever an Query definition is added or removed.
addQueryIdsListener(listener: QueryIdsListener): string| Type | Description | |
|---|---|---|
listener | QueryIdsListener | The function that will be called whenever a Query definition is added or removed. |
| returns | string |
The provided listener is a QueryIdsListener function, and will be called with a reference to the Queries object.
Example
This example creates a Store, a Queries object, and then registers a listener that responds to the addition and the removal of a Query definition.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store);
const listenerId = queries.addQueryIdsListener((queries) => {
console.log(queries.getQueryIds());
});
queries.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
// -> ['dogColors']
queries.delQueryDefinition('dogColors');
// -> []
queries.delListener(listenerId);
Since
v4.1.0
delListener
The delListener method removes a listener that was previously added to the Queries object.
delListener(listenerId: string): Queries| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | Queries | A reference to the |
Use the Id returned by the addMetricListener method. Note that the Queries object may re-use this Id for future listeners added to it.
Example
This example creates a Store, a Queries object, registers a listener, and then removes it.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const queries = createQueries(store).setQueryDefinition(
'species',
'pets',
({select}) => {
select('species');
},
);
const listenerId = queries.addResultTableListener('species', () =>
console.log('species result changed'),
);
store.setCell('pets', 'ed', 'species', 'horse');
// -> 'species result changed'
queries.delListener(listenerId);
store.setCell('pets', 'molly', 'species', 'cow');
// -> undefined
// The listener is not called.
Since
v2.0.0
Configuration methods
delQueryDefinition
The delQueryDefinition method removes an existing query definition.
delQueryDefinition(queryId: string): Queries| Type | Description | |
|---|---|---|
queryId | string | The |
| returns | Queries | A reference to the |
Example
This example creates a Store, creates a Queries object, defines a simple query, and then removes it.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store);
queries.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
console.log(queries.getQueryIds());
// -> ['dogColors']
queries.delQueryDefinition('dogColors');
console.log(queries.getQueryIds());
// -> []
Since
v2.0.0
setQueryDefinition
The setQueryDefinition method lets you set the definition of a query.
setQueryDefinition(
queryId: string,
tableId: string,
query: (keywords: {
select: Select;
join: Join;
where: Where;
group: Group;
having: Having;
}) => void,
): Queries| Type | Description | |
|---|---|---|
queryId | string | The |
tableId | string | |
query | (keywords: { select: Select; join: Join; where: Where; group: Group; having: Having; }) => void | A callback which can take a |
| returns | Queries | A reference to the |
Every query definition is identified by a unique Id, and if you re-use an existing Id with this method, the previous definition is overwritten.
A query provides a tabular result formed from each Row within a root Table. The definition must specify this Table (by its Id) to be aggregated. Other Tables can be joined to that using Join clauses.
The third query parameter is a callback that you provide to define the query. That callback is provided with a keywords object that contains the functions you use to define the query, like select, join, and so on. You can see how that is used in the simple example below. The following five clause types are supported:
- The
Selecttype describes a function that lets you specify aCellor calculated value for including into the query's result. - The
Jointype describes a function that lets you specify aCellor calculated value to join the main queryTableto others, byRowId. - The
Wheretype describes a function that lets you specify conditions to filter results, based on the underlying Cells of the main or joinedTables. - The
Grouptype describes a function that lets you specify that the values of aCellin multiple ResultRows should be aggregated together. - The
Havingtype describes a function that lets you specify conditions to filter results, based on the grouped Cells resulting from aGroupclause.
Full documentation and examples are provided in the sections for each of those clause types.
Additionally, you can use the getResultSortedRowIds method and addResultSortedRowIdsListener method to sort and paginate the results.
Example
This example creates a Store, creates a Queries object, and defines a simple query to select just one column from the Table, for each Row where the species Cell matches as certain value.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store);
queries.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
console.log(queries.getResultTable('dogColors'));
// -> {fido: {color: 'brown'}, cujo: {color: 'black'}}
Since
v2.0.0
Iterator methods
forEachResultTable
The forEachResultTable method takes a function that it will then call for each ResultTable in the Queries object.
forEachResultTable(tableCallback: ResultTableCallback): void| Type | Description | |
|---|---|---|
tableCallback | ResultTableCallback | The function that should be called for every query's |
| returns | void | This has no return value. |
This method is useful for iterating over all the ResultTables of the queries in a functional style. The tableCallback parameter is a ResultTableCallback function that will be called with the Id of each ResultTable, and with a function that can then be used to iterate over each ResultRow of the ResultTable, should you wish.
Example
This example iterates over each query's ResultTable in a Queries object.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store)
.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
})
.setQueryDefinition('catColors', 'pets', ({select, where}) => {
select('color');
where('species', 'cat');
});
queries.forEachResultTable((queryId, forEachRow) => {
console.log(queryId);
forEachRow((rowId) => console.log(`- ${rowId}`));
});
// -> 'dogColors'
// -> '- fido'
// -> '- cujo'
// -> 'catColors'
// -> '- felix'
Since
v2.0.0
forEachResultRow
The forEachResultRow method takes a function that it will then call for each ResultRow in the ResultTable of a query.
forEachResultRow(
queryId: string,
rowCallback: ResultRowCallback,
): void| Type | Description | |
|---|---|---|
queryId | string | The |
rowCallback | ResultRowCallback | The function that should be called for every |
| returns | void | This has no return value. |
This method is useful for iterating over each ResultRow of the ResultTable of the query in a functional style. The rowCallback parameter is a ResultRowCallback function that will be called with the Id of each ResultRow, and with a function that can then be used to iterate over each ResultCell of the ResultRow, should you wish.
Example
This example iterates over each ResultRow in a query's ResultTable.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
queries.forEachResultRow('dogColors', (rowId, forEachCell) => {
console.log(rowId);
forEachCell((cellId) => console.log(`- ${cellId}`));
});
// -> 'fido'
// -> '- color'
// -> 'cujo'
// -> '- color'
Since
v2.0.0
forEachResultCell
The forEachResultCell method takes a function that it will then call for each ResultCell in the ResultRow of a query.
forEachResultCell(
queryId: string,
rowId: string,
cellCallback: ResultCellCallback,
): void| Type | Description | |
|---|---|---|
queryId | string | The |
rowId | string | The |
cellCallback | ResultCellCallback | The function that should be called for every |
| returns | void | This has no return value. |
This method is useful for iterating over each ResultCell of the ResultRow of the query in a functional style. The cellCallback parameter is a ResultCellCallback function that will be called with the Id and value of each ResultCell.
Example
This example iterates over each ResultCell in a query's ResultRow.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('species');
select('color');
where('species', 'dog');
},
);
queries.forEachResultCell('dogColors', 'fido', (cellId, cell) => {
console.log(`${cellId}: ${cell}`);
});
// -> 'species: dog'
// -> 'color: brown'
Since
v2.0.0
forEachQuery
The forEachQuery method takes a function that it will then call for each Query in the Queries object.
forEachQuery(queryCallback: QueryCallback): void| Type | Description | |
|---|---|---|
queryCallback | QueryCallback | The function that should be called for every query. |
| returns | void | This has no return value. |
This method is useful for iterating over all the queries in a functional style. The queryCallback parameter is a QueryCallback function that will be called with the Id of each query.
Example
This example iterates over each query in a Queries object.
import {createQueries, createStore} from 'tinybase';
const queries = createQueries(createStore())
.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
})
.setQueryDefinition('catColors', 'pets', ({select, where}) => {
select('color');
where('species', 'cat');
});
queries.forEachQuery((queryId) => {
console.log(queryId);
});
// -> 'dogColors'
// -> 'catColors'
Since
v2.0.0
Lifecycle methods
destroy
The destroy method should be called when this Queries object is no longer used.
destroy(): voidThis guarantees that all of the listeners that the object registered with the underlying Store are removed and it can be correctly garbage collected.
Example
This example creates a Store, adds a Queries object with a definition (that registers a RowListener with the underlying Store), and then destroys it again, removing the listener.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const queries = createQueries(store);
queries.setQueryDefinition('species', 'species', ({select}) => {
select('species');
});
console.log(store.getListenerStats().row);
// -> 1
queries.destroy();
console.log(store.getListenerStats().row);
// -> 0
Since
v2.0.0
Development methods
getListenerStats
The getListenerStats method provides a set of statistics about the listeners registered with the Queries object, and is used for debugging purposes.
getListenerStats(): QueriesListenerStats| returns | QueriesListenerStats | A |
|---|
The method is intended to be used during development to ensure your application is not leaking listener registrations, for example.
Example
This example gets the listener statistics of a Queries object.
import {createQueries, createStore} from 'tinybase';
const store = createStore();
const queries = createQueries(store);
queries.addResultTableListener(null, () => console.log('Result changed'));
console.log(queries.getListenerStats().table);
// -> 1
console.log(queries.getListenerStats().row);
// -> 0
Since
v2.0.0
Functions
createQueries
The createQueries function creates a Queries object, and is the main entry point into the queries module.
createQueries(store: Store): Queries| Type | Description | |
|---|---|---|
store | Store | The |
| returns | Queries | A reference to the new |
A given Store can only have one Queries object associated with it. If you call this function twice on the same Store, your second call will return a reference to the Queries object created by the first.
Examples
This example creates a Queries object.
import {createQueries, createStore} from 'tinybase';
const store = createStore();
const queries = createQueries(store);
console.log(queries.getQueryIds());
// -> []
This example creates a Queries object, and calls the method a second time for the same Store to return the same object.
import {createQueries, createStore} from 'tinybase';
const store = createStore();
const queries1 = createQueries(store);
const queries2 = createQueries(store);
console.log(queries1 === queries2);
// -> true
Since
v2.0.0
Type Aliases
Definition type aliases
Group
The Group type describes a function that lets you specify that the values of a Cell in multiple ResultRows should be aggregated together.
(
selectedCellId: Id,
aggregate: "count" | "sum" | "avg" | "min" | "max" | Aggregate,
aggregateAdd?: AggregateAdd,
aggregateRemove?: AggregateRemove,
aggregateReplace?: AggregateReplace,
): GroupedAs| Type | Description | |
|---|---|---|
selectedCellId | Id | The |
aggregate | "count" | "sum" | "avg" | "min" | "max" | Aggregate | Either a string representing one of a set of common aggregation techniques ('count', 'sum', 'avg', 'min', or 'max'), or a function that aggregates |
aggregateAdd? | AggregateAdd | A function that can be used to optimize a custom |
aggregateRemove? | AggregateRemove | A function that can be used to optimize a custom |
aggregateReplace? | AggregateReplace | A function that can be used to optimize a custom |
| returns | GroupedAs | A |
The Group function is provided to the third query parameter of the setQueryDefinition method. When called, it should refer to a Cell Id (or aliased Id) specified in one of the Select functions, and indicate how the values should be aggregated.
This is applied after any joins or where-based filtering.
If you provide a Group for every Select, the result will be a single Row with every Cell having been aggregated. If you provide a Group for only one, or some, of the Select clauses, the others will be automatically used as dimensional values (analogous to the group by semantics in SQL), within which the aggregations of Group Cells will be performed.
You can join the same underlying Cell multiple times, but in that case you will need to use the 'as' function to distinguish them from each other.
The second parameter can be one of five predefined aggregates - 'count', 'sum', 'avg', 'min', and 'max' - or a custom function that produces your own aggregation of an array of Cell values.
The final three parameters, aggregateAdd, aggregateRemove, aggregateReplace need only be provided when you are using your own custom aggregate function. These give you the opportunity to reduce your custom function's algorithmic complexity by providing shortcuts that can nudge an aggregation result when a single value is added, removed, or replaced in the input values.
Examples
This example shows a query that calculates the average of all the values in a single selected Cell from a joined Table.
import {createQueries, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
lowly: {species: 'worm'},
})
.setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join, group}) => {
select('species', 'price');
// from pets
join('species', 'species');
group('price', 'avg').as('avgPrice');
});
console.log(queries.getResultTable('query'));
// -> {0: {avgPrice: 3.75}}
// 2 dogs at 5, 1 cat at 4, 1 worm at 1: a total of 15 for 4 pets
This example shows a query that calculates the average of a two Cell values, aggregated by the two other dimensional 'group by' Cells.
import {createQueries, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', owner: 'alice'},
felix: {species: 'cat', owner: 'bob'},
cujo: {species: 'dog', owner: 'bob'},
lowly: {species: 'worm', owner: 'alice'},
carnaby: {species: 'parrot', owner: 'bob'},
polly: {species: 'parrot', owner: 'alice'},
})
.setTable('species', {
dog: {price: 5, legs: 4},
cat: {price: 4, legs: 4},
parrot: {price: 3, legs: 2},
worm: {price: 1, legs: 0},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join, group}) => {
select('pets', 'owner'); // group by
select('species', 'price'); // grouped
// from pets
join('species', 'species');
group(
'price',
(cells) => Math.min(...cells.filter((cell) => cell > 2)),
(current, add) => (add > 2 ? Math.min(current, add) : current),
(current, remove) => (remove == current ? undefined : current),
(current, add, remove) =>
remove == current
? undefined
: add > 2
? Math.min(current, add)
: current,
).as('lowestPriceOver2');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {0: {owner: 'alice', lowestPriceOver2: 3}}
// -> {1: {owner: 'bob', lowestPriceOver2: 3}}
// Both have a parrot at 3. Alice's worm at 1 is excluded from aggregation.
Since
v2.0.0
GroupedAs
The GroupedAs type describes an object returned from calling a Group function so that the grouped Cell Id can be optionally aliased.
{as: (groupedCellId: Id) => void}| Type | Description | |
|---|---|---|
as | (groupedCellId: Id) => void | A function that lets you specify an alias for the grouped |
Note that if two Group clauses are both aliased to the same name (or if you create two groups of the same underlying Cell, both without aliases), only the latter of two will be used in the query.
Example
This example shows a query that groups the same underlying Cell twice, for different purposes. Both groups are aliased with the 'as' function to disambiguate them.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', price: 5},
felix: {species: 'cat', price: 4},
cujo: {species: 'dog', price: 4},
tom: {species: 'cat', price: 3},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, group}) => {
select('pets', 'species');
select('pets', 'price');
group('price', 'min').as('minPrice');
group('price', 'max').as('maxPrice');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {0: {species: 'dog', minPrice: 4, maxPrice: 5}}
// -> {1: {species: 'cat', minPrice: 3, maxPrice: 4}}
Since
v2.0.0
Having
The Having type describes a function that lets you specify conditions to filter results, based on the grouped Cells resulting from a Group clause.
Calling this function with two parameters is used to include only those Rows for which a specified Cell in the query's root Table has a specified value.
(
selectedOrGroupedCellId: string,
equals: Cell,
): void| Type | Description | |
|---|---|---|
selectedOrGroupedCellId | string | |
equals | Cell | The value that the |
| returns | void | This has no return value. |
Since
v2.0.0
Calling this function with one callback parameter is used to include only those Rows which meet a calculated boolean condition.
(condition: (getSelectedOrGroupedCell: GetCell) => boolean): void| Type | Description | |
|---|---|---|
condition | (getSelectedOrGroupedCell: GetCell) => boolean | A callback that takes a |
| returns | void | This has no return value. |
Since
v2.0.0
The Having function is provided to the third query parameter of the setQueryDefinition method.
A Having condition has to be true for a Row to be included in the results. Each Having class is additive, as though combined with a logical 'and'. If you wish to create an 'or' expression, use the single parameter version of the type that allows arbitrary programmatic conditions.
The Where keyword differs from the Having keyword in that the former describes conditions that should be met by underlying Cell values (whether selected or not), and the latter describes conditions based on calculated and aggregated values - after Group clauses have been applied.
Whilst it is technically possible to use a Having clause even if the results have not been grouped with a Group clause, you should expect it to be less performant than using a Where clause, due to that being applied earlier in the query process.
Examples
This example shows a query that filters the results from a grouped Table by comparing a Cell from it with a value.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', price: 5},
felix: {species: 'cat', price: 4},
cujo: {species: 'dog', price: 4},
tom: {species: 'cat', price: 3},
carnaby: {species: 'parrot', price: 3},
polly: {species: 'parrot', price: 3},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, group, having}) => {
select('pets', 'species');
select('pets', 'price');
group('price', 'min').as('minPrice');
group('price', 'max').as('maxPrice');
having('minPrice', 3);
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {0: {species: 'cat', minPrice: 3, maxPrice: 4}}
// -> {1: {species: 'parrot', minPrice: 3, maxPrice: 3}}
This example shows a query that filters the results from a grouped Table with a condition that is calculated from Cell values.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', price: 5},
felix: {species: 'cat', price: 4},
cujo: {species: 'dog', price: 4},
tom: {species: 'cat', price: 3},
carnaby: {species: 'parrot', price: 3},
polly: {species: 'parrot', price: 3},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, group, having}) => {
select('pets', 'species');
select('pets', 'price');
group('price', 'min').as('minPrice');
group('price', 'max').as('maxPrice');
having(
(getSelectedOrGroupedCell) =>
getSelectedOrGroupedCell('minPrice') !=
getSelectedOrGroupedCell('maxPrice'),
);
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {0: {species: 'dog', minPrice: 4, maxPrice: 5}}
// -> {1: {species: 'cat', minPrice: 3, maxPrice: 4}}
// Parrots are filtered out because they have zero range in price.
Since
v2.0.0
Join
The Join type describes a function that lets you specify a Cell or calculated value to join the main query Table to other Tables, by their Row Id.
Calling this function with two Id parameters will indicate that the join to a Row in an adjacent Table is made by finding its Id in a Cell of the query's root Table.
(
joinedTableId: string,
on: string,
): JoinedAs| Type | Description | |
|---|---|---|
joinedTableId | string | |
on | string | The |
| returns | JoinedAs | A |
Since
v2.0.0
Calling this function with two parameters (where the second is a function) will indicate that the join to a Row in an adjacent Table is made by calculating its Id from the Cells and the Row Id of the query's root Table.
(
joinedTableId: string,
on: (getCell: GetCell, rowId: string) => undefined | string,
): JoinedAs| Type | Description | |
|---|---|---|
joinedTableId | string | |
on | (getCell: GetCell, rowId: string) => undefined | string | A callback that takes a |
| returns | JoinedAs | A |
Since
v2.0.0
Calling this function with three Id parameters will indicate that the join to a Row in distant Table is made by finding its Id in a Cell of an intermediately joined Table.
(
joinedTableId: string,
fromIntermediateJoinedTableId: string,
on: string,
): JoinedAs| Type | Description | |
|---|---|---|
joinedTableId | string | |
fromIntermediateJoinedTableId | string | The |
on | string | The |
| returns | JoinedAs | A |
Since
v2.0.0
Calling this function with three parameters (where the third is a function) will indicate that the join to a Row in distant Table is made by calculating its Id from the Cells and the Row Id of an intermediately joined Table.
(
joinedTableId: string,
fromIntermediateJoinedTableId: string,
on: (getIntermediateJoinedCell: GetCell, intermediateJoinedRowId: string) => undefined | string,
): JoinedAs| Type | Description | |
|---|---|---|
joinedTableId | string | |
fromIntermediateJoinedTableId | string | The |
on | (getIntermediateJoinedCell: GetCell, intermediateJoinedRowId: string) => undefined | string | A callback that takes a |
| returns | JoinedAs | A |
Since
v2.0.0
The Join function is provided to the third query parameter of the setQueryDefinition method.
You can join zero, one, or many Tables. You can join the same underlying Table multiple times, but in that case you will need to use the 'as' function to distinguish them from each other.
By default, each join is made from the main query Table to the joined table, but it is also possible to connect via an intermediate join Table to a more distant join Table.
Because a Join clause is used to identify which unique Row Id of the joined Table will be joined to each Row of the root Table, queries follow the 'left join' semantics you may be familiar with from SQL. This means that an unfiltered query will only ever return the same number of Rows as the main Table being queried, and indeed the resulting table (assuming it has not been aggregated) will even preserve the root Table's original Row Ids.
Examples
This example shows a query that joins a single Table by using an Id present in the main query Table.
import {createQueries, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', ownerId: '1'},
felix: {species: 'cat', ownerId: '2'},
cujo: {species: 'dog', ownerId: '3'},
})
.setTable('owners', {
'1': {name: 'Alice'},
'2': {name: 'Bob'},
'3': {name: 'Carol'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join}) => {
select('species');
select('owners', 'name');
// from pets
join('owners', 'ownerId');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {species: 'dog', name: 'Alice'}}
// -> {felix: {species: 'cat', name: 'Bob'}}
// -> {cujo: {species: 'dog', name: 'Carol'}}
This example shows a query that joins the same underlying Table twice, and aliases them (and the selected Cell Ids). Note the left-join semantics: Felix the cat was bought, but the seller was unknown. The record still exists in the ResultTable.
import {createQueries, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', buyerId: '1', sellerId: '2'},
felix: {species: 'cat', buyerId: '2'},
cujo: {species: 'dog', buyerId: '3', sellerId: '1'},
})
.setTable('humans', {
'1': {name: 'Alice'},
'2': {name: 'Bob'},
'3': {name: 'Carol'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join}) => {
select('buyers', 'name').as('buyer');
select('sellers', 'name').as('seller');
// from pets
join('humans', 'buyerId').as('buyers');
join('humans', 'sellerId').as('sellers');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {buyer: 'Alice', seller: 'Bob'}}
// -> {felix: {buyer: 'Bob'}}
// -> {cujo: {buyer: 'Carol', seller: 'Alice'}}
This example shows a query that calculates the Id of the joined Table based from multiple values in the root Table rather than a single Cell.
import {createQueries, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
})
.setTable('colorSpecies', {
'brown-dog': {price: 6},
'black-dog': {price: 5},
'brown-cat': {price: 4},
'black-cat': {price: 3},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join}) => {
select('colorSpecies', 'price');
// from pets
join(
'colorSpecies',
(getCell) => `${getCell('color')}-${getCell('species')}`,
);
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {price: 6}}
// -> {felix: {price: 3}}
// -> {cujo: {price: 5}}
This example shows a query that joins two Tables, one through the intermediate other.
import {createQueries, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', ownerId: '1'},
felix: {species: 'cat', ownerId: '2'},
cujo: {species: 'dog', ownerId: '3'},
})
.setTable('owners', {
'1': {name: 'Alice', state: 'CA'},
'2': {name: 'Bob', state: 'CA'},
'3': {name: 'Carol', state: 'WA'},
})
.setTable('states', {
CA: {name: 'California'},
WA: {name: 'Washington'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join}) => {
select(
(getTableCell) =>
`${getTableCell('species')} in ${getTableCell('states', 'name')}`,
).as('description');
// from pets
join('owners', 'ownerId');
join('states', 'owners', 'state');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {description: 'dog in California'}}
// -> {felix: {description: 'cat in California'}}
// -> {cujo: {description: 'dog in Washington'}}
Since
v2.0.0
JoinedAs
The JoinedAs type describes an object returned from calling a Join function so that the joined Table Id can be optionally aliased.
{as: (joinedTableId: Id) => void}| Type | Description | |
|---|---|---|
as | (joinedTableId: Id) => void | A function that lets you specify an alias for the joined |
Note that if two Join clauses are both aliased to the same name (or if you create two joins to the same underlying Table, both without aliases), only the latter of two will be used in the query.
For the purposes of clarity, it's recommended to use an alias that does not collide with a real underlying Table (whether included in the query or not).
Example
This example shows a query that joins the same underlying Table twice, for different purposes. Both joins are aliased with the 'as' function to disambiguate them. Note that the selected Cells are also aliased.
import {createQueries, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', buyerId: '1', sellerId: '2'},
felix: {species: 'cat', buyerId: '2'},
cujo: {species: 'dog', buyerId: '3', sellerId: '1'},
})
.setTable('humans', {
'1': {name: 'Alice'},
'2': {name: 'Bob'},
'3': {name: 'Carol'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join}) => {
select('buyers', 'name').as('buyer');
select('sellers', 'name').as('seller');
// from pets
join('humans', 'buyerId').as('buyers');
join('humans', 'sellerId').as('sellers');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {buyer: 'Alice', seller: 'Bob'}}
// -> {felix: {buyer: 'Bob'}}
// -> {cujo: {buyer: 'Carol', seller: 'Alice'}}
Since
v2.0.0
Select
The Select type describes a function that lets you specify a Cell or calculated value for including into the query's result.
Calling this function with one Id parameter will indicate that the query should select the value of the specified Cell from the query's root Table.
(cellId: string): SelectedAs| Type | Description | |
|---|---|---|
cellId | string | |
| returns | SelectedAs | A |
Since
v2.0.0
Calling this function with two parameters will indicate that the query should select the value of the specified Cell from a Table that has been joined in the query.
(
joinedTableId: string,
joinedCellId: string,
): SelectedAs| Type | Description | |
|---|---|---|
joinedTableId | string | The |
joinedCellId | string | |
| returns | SelectedAs | A |
Since
v2.0.0
Calling this function with one callback parameter will indicate that the query should select a calculated value, based on one or more Cell values in the root Table or a joined Table, or on the root Table's Row Id.
(getCell: (getTableCell: GetTableCell, rowId: string) => ResultCellOrUndefined): SelectedAs| Type | Description | |
|---|---|---|
getCell | (getTableCell: GetTableCell, rowId: string) => ResultCellOrUndefined | A callback that takes a |
| returns | SelectedAs | A |
Since
v2.0.0
The Select function is provided to the third query parameter of the setQueryDefinition method. A query definition must call the Select function at least once, otherwise it will be meaningless and return no data.
Examples
This example shows a query that selects two Cells from the main query Table.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown', legs: 4},
felix: {species: 'cat', color: 'black', legs: 4},
cujo: {species: 'dog', color: 'black', legs: 4},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select}) => {
select('species');
select('color');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {species: 'dog', color: 'brown'}}
// -> {felix: {species: 'cat', color: 'black'}}
// -> {cujo: {species: 'dog', color: 'black'}}
This example shows a query that selects two Cells, one from a joined Table.
import {createQueries, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', ownerId: '1'},
felix: {species: 'cat', ownerId: '2'},
cujo: {species: 'dog', ownerId: '3'},
})
.setTable('owners', {
'1': {name: 'Alice'},
'2': {name: 'Bob'},
'3': {name: 'Carol'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join}) => {
select('species');
select('owners', 'name');
// from pets
join('owners', 'ownerId');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {species: 'dog', name: 'Alice'}}
// -> {felix: {species: 'cat', name: 'Bob'}}
// -> {cujo: {species: 'dog', name: 'Carol'}}
This example shows a query that calculates a value from two underlying Cells.
import {createQueries, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', ownerId: '1'},
felix: {species: 'cat', ownerId: '2'},
cujo: {species: 'dog', ownerId: '3'},
})
.setTable('owners', {
'1': {name: 'Alice'},
'2': {name: 'Bob'},
'3': {name: 'Carol'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join}) => {
select(
(getTableCell) =>
`${getTableCell('species')} for ${getTableCell('owners', 'name')}`,
).as('description');
join('owners', 'ownerId');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {description: 'dog for Alice'}}
// -> {felix: {description: 'cat for Bob'}}
// -> {cujo: {description: 'dog for Carol'}}
Since
v2.0.0
SelectedAs
The SelectedAs type describes an object returned from calling a Select function so that the selected Cell Id can be optionally aliased.
{as: (selectedCellId: Id) => void}| Type | Description | |
|---|---|---|
as | (selectedCellId: Id) => void |
If you are using a callback in the Select cause, it is highly recommended to use the 'as' function, since otherwise a machine-generated column name will be used.
Note that if two Select clauses are both aliased to the same name (or if two columns with the same underlying name are selected, both without aliases), only the latter of two will be used in the query.
Example
This example shows a query that selects two Cells, one from a joined Table. Both are aliased with the 'as' function:
import {createQueries, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', ownerId: '1'},
felix: {species: 'cat', ownerId: '2'},
cujo: {species: 'dog', ownerId: '3'},
})
.setTable('owners', {
'1': {name: 'Alice'},
'2': {name: 'Bob'},
'3': {name: 'Carol'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join}) => {
select('species').as('petSpecies');
select('owners', 'name').as('ownerName');
// from pets
join('owners', 'ownerId');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {petSpecies: 'dog', ownerName: 'Alice'}}
// -> {felix: {petSpecies: 'cat', ownerName: 'Bob'}}
// -> {cujo: {petSpecies: 'dog', ownerName: 'Carol'}}
Since
v2.0.0
Where
The Where type describes a function that lets you specify conditions to filter results, based on the underlying Cells of the root or joined Tables.
Calling this function with two parameters is used to include only those Rows for which a specified Cell in the query's root Table has a specified value.
(
cellId: string,
equals: Cell,
): void| Type | Description | |
|---|---|---|
cellId | string | |
equals | Cell | The value that the |
| returns | void | This has no return value. |
Since
v2.0.0
Calling this function with three parameters is used to include only those Rows for which a specified Cell in a joined Table has a specified value.
(
joinedTableId: string,
joinedCellId: string,
equals: Cell,
): void| Type | Description | |
|---|---|---|
joinedTableId | string | The |
joinedCellId | string | |
equals | Cell | The value that the |
| returns | void | This has no return value. |
Since
v2.0.0
Calling this function with one callback parameter is used to include only those Rows which meet a calculated boolean condition, based on values in the main and (optionally) joined Tables.
(condition: (getTableCell: GetTableCell) => boolean): void| Type | Description | |
|---|---|---|
condition | (getTableCell: GetTableCell) => boolean | A callback that takes a |
| returns | void | This has no return value. |
Since
v2.0.0
The Where function is provided to the third query parameter of the setQueryDefinition method.
If you do not specify a Where clause, you should expect every non-empty Row of the root Table to appear in the query's results.
A Where condition has to be true for a Row to be included in the results. Each Where class is additive, as though combined with a logical 'and'. If you wish to create an 'or' expression, use the single parameter version of the type that allows arbitrary programmatic conditions.
The Where keyword differs from the Having keyword in that the former describes conditions that should be met by underlying Cell values (whether selected or not), and the latter describes conditions based on calculated and aggregated values - after Group clauses have been applied.
Examples
This example shows a query that filters the results from a single Table by comparing an underlying Cell from it with a value.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, where}) => {
select('species');
where('species', 'dog');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {species: 'dog'}}
// -> {cujo: {species: 'dog'}}
This example shows a query that filters the results of a query by comparing an underlying Cell from a joined Table with a value. Note that the joined table has also been aliased, and so its alias is used in the Where clause.
import {createQueries, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', ownerId: '1'},
felix: {species: 'cat', ownerId: '2'},
cujo: {species: 'dog', ownerId: '3'},
})
.setTable('owners', {
'1': {name: 'Alice', state: 'CA'},
'2': {name: 'Bob', state: 'CA'},
'3': {name: 'Carol', state: 'WA'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join, where}) => {
select('species');
// from pets
join('owners', 'ownerId').as('petOwners');
where('petOwners', 'state', 'CA');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {species: 'dog'}}
// -> {felix: {species: 'cat'}}
This example shows a query that filters the results of a query with a condition that is calculated from underlying Cell values from the main and joined Table. Note that the joined table has also been aliased, and so its alias is used in the Where clause.
import {createQueries, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', ownerId: '1'},
felix: {species: 'cat', ownerId: '2'},
cujo: {species: 'dog', ownerId: '3'},
})
.setTable('owners', {
'1': {name: 'Alice', state: 'CA'},
'2': {name: 'Bob', state: 'CA'},
'3': {name: 'Carol', state: 'WA'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join, where}) => {
select('species');
select('petOwners', 'state');
// from pets
join('owners', 'ownerId').as('petOwners');
where(
(getTableCell) =>
getTableCell('pets', 'species') === 'cat' ||
getTableCell('petOwners', 'state') === 'WA',
);
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {felix: {species: 'cat', state: 'CA'}}
// -> {cujo: {species: 'dog', state: 'WA'}}
Since
v2.0.0
Result type aliases
ResultTable
The ResultTable type is the data structure representing the results of a query.
{[rowId: Id]: ResultRow}A ResultTable is typically accessed with the getResultTable method or addResultTableListener method. It is similar to the Table type in the store module, but without schema-specific typing, and is a regular JavaScript object containing individual ResultRow objects, keyed by their Id.
Example
import type {ResultTable} from 'tinybase';
export const resultTable: ResultTable = {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat'},
};
Since
v2.0.0
ResultRow
The ResultRow type is the data structure representing a single row in the results of a query.
{[cellId: Id]: ResultCell}A ResultRow is typically accessed with the getResultRow method or addResultRowListener method. It is similar to the Row type in the store module, but without schema-specific typing, and is a regular JavaScript object containing individual ResultCell objects, keyed by their Id.
Example
import type {ResultRow} from 'tinybase';
export const resultRow: ResultRow = {species: 'dog', color: 'brown'};
Since
v2.0.0
ResultCell
The ResultCell type is the data structure representing a single cell in the results of a query.
string | number | booleanA ResultCell is typically accessed with the getResultCell method or addResultCellListener method. It is similar to the Cell type in the store module, but without schema-specific typing, and is a JavaScript string, number, or boolean.
Example
import type {ResultCell} from 'tinybase';
export const resultCell: ResultCell = 'dog';
Since
v2.0.0
ResultCellOrUndefined
The ResultCellOrUndefined type is the data structure representing a single cell in the results of a query, or the value undefined.
ResultCell | undefinedSince
v2.0.0
Listener type aliases
ResultTableCellIdsListener
The ResultTableCellIdsListener type describes a function that is used to listen to changes to the Cell Ids that appear anywhere in a query's ResultTable.
(
queries: Queries,
tableId: Id,
getIdChanges: GetIdChanges | undefined,
): void| Type | Description | |
|---|---|---|
queries | Queries | A reference to the |
tableId | Id | The |
getIdChanges | GetIdChanges | undefined | |
| returns | void | This has no return value. |
A ResultTableCellIdsListener is provided when using the addResultTableCellIdsListener method. See that method for specific examples.
When called, a ResultTableCellIdsListener is given a reference to the Queries object, and the Id of the ResultTable whose Cell Ids changed (which is the same as the query Id).
You can create new query definitions within the body of this listener, though obviously be aware of the possible cascading effects of doing so.
Since
v4.1.0
ResultTableListener
The ResultTableListener type describes a function that is used to listen to changes to a query's ResultTable.
(
queries: Queries,
tableId: Id,
getCellChange: GetResultCellChange,
): void| Type | Description | |
|---|---|---|
queries | Queries | A reference to the |
tableId | Id | The |
getCellChange | GetResultCellChange | A function that returns information about any |
| returns | void | This has no return value. |
A ResultTableListener is provided when using the addResultTableListener method. See that method for specific examples.
When called, a ResultTableListener is given a reference to the Queries object, the Id of the ResultTable that changed (which is the same as the query Id), and a GetResultCellChange function that can be used to query ResultCell values before and after the change.
You can create new query definitions within the body of this listener, though obviously be aware of the possible cascading effects of doing so.
Since
v2.0.0
ResultRowIdsListener
The ResultRowIdsListener type describes a function that is used to listen to changes to the ResultRow Ids in a query's ResultTable.
(
queries: Queries,
tableId: Id,
getIdChanges: GetIdChanges | undefined,
): void| Type | Description | |
|---|---|---|
queries | Queries | A reference to the |
tableId | Id | The |
getIdChanges | GetIdChanges | undefined | |
| returns | void | This has no return value. |
A ResultRowIdsListener is provided when using the addResultRowIdsListener method. See that method for specific examples.
When called, a ResultRowIdsListener is given a reference to the Queries object, and the Id of the ResultTable whose ResultRow Ids changed (which is the same as the query Id).
You can create new query definitions within the body of this listener, though obviously be aware of the possible cascading effects of doing so.
Since
v2.0.0
ResultSortedRowIdsListener
The ResultSortedRowIdsListener type describes a function that is used to listen to changes to the sorted ResultRow Ids in a query's ResultTable.
(
queries: Queries,
tableId: Id,
cellId: Id | undefined,
descending: boolean,
offset: number,
limit: number | undefined,
sortedRowIds: Ids,
): void| Type | Description | |
|---|---|---|
queries | Queries | A reference to the |
tableId | Id | The |
cellId | Id | undefined | The |
descending | boolean | Whether the sorting was in descending order. |
offset | number | |
limit | number | undefined | |
sortedRowIds | Ids | |
| returns | void | This has no return value. |
A ResultSortedRowIdsListener is provided when using the addResultSortedRowIdsListener method. See that method for specific examples.
When called, a ResultSortedRowIdsListener is given a reference to the Queries object, the Id of the ResultTable whose ResultRow Ids changed (which is the same as the query Id), the ResultCell Id being used to sort them, whether descending or not, and the offset and limit of the number of Ids returned, for pagination purposes. It also receives the sorted array of Ids itself, so that you can use them in the listener without the additional cost of an explicit call to getResultSortedRowIds.
You can create new query definitions within the body of this listener, though obviously be aware of the possible cascading effects of doing so.
Since
v2.0.0
ResultRowCountListener
The ResultRowCountListener type describes a function that is used to listen to changes to the number of ResultRow objects in a query's ResultTable.
(
queries: Queries,
tableId: Id,
count: number,
): void| Type | Description | |
|---|---|---|
queries | Queries | A reference to the |
tableId | Id | The |
count | number | The number of |
| returns | void | This has no return value. |
A ResultRowCountListener is provided when using the addResultRowCountListener method. See that method for specific examples.
When called, a ResultRowCountListener is given a reference to the Queries object, the Id of the ResultTable whose ResultRow Ids changed (which is the same as the query Id), and the count of ResultRow objects in the ResultTable.
You can create new query definitions within the body of this listener, though obviously be aware of the possible cascading effects of doing so.
Since
v4.1.0
ResultRowListener
The ResultRowListener type describes a function that is used to listen to changes to a ResultRow in a query's ResultTable.
(
queries: Queries,
tableId: Id,
rowId: Id,
getCellChange: GetResultCellChange,
): void| Type | Description | |
|---|---|---|
queries | Queries | A reference to the |
tableId | Id | The |
rowId | Id | |
getCellChange | GetResultCellChange | A function that returns information about any |
| returns | void | This has no return value. |
A ResultRowListener is provided when using the addResultRowListener method. See that method for specific examples.
When called, a ResultRowListener is given a reference to the Queries object, the Id of the ResultTable that changed (which is the same as the query Id), the Id of the ResultRow that changed, and a GetResultCellChange function that can be used to query ResultCell values before and after the change.
You can create new query definitions within the body of this listener, though obviously be aware of the possible cascading effects of doing so.
Since
v2.0.0
ResultCellIdsListener
The ResultCellIdsListener type describes a function that is used to listen to changes to the ResultCell Ids in a ResultRow in a query's ResultTable.
(
queries: Queries,
tableId: Id,
rowId: Id,
getIdChanges: GetIdChanges | undefined,
): void| Type | Description | |
|---|---|---|
queries | Queries | A reference to the |
tableId | Id | The |
rowId | Id | |
getIdChanges | GetIdChanges | undefined | |
| returns | void | This has no return value. |
A ResultCellIdsListener is provided when using the addResultCellIdsListener method. See that method for specific examples.
When called, a ResultCellIdsListener is given a reference to the Queries object, the Id of the ResultTable that changed (which is the same as the query Id), and the Id of the ResultRow whose ResultCell Ids changed.
You can create new query definitions within the body of this listener, though obviously be aware of the possible cascading effects of doing so.
Since
v2.0.0
GetResultCellChange
The GetResultCellChange type describes a function that returns information about any ResultCell's changes during a transaction.
(
tableId: Id,
rowId: Id,
cellId: Id,
): ResultCellChange| Type | Description | |
|---|---|---|
tableId | Id | The |
rowId | Id | |
cellId | Id | The |
| returns | ResultCellChange | A |
A GetResultCellChange function is provided to every listener when called due the Store changing. The listener can then fetch the previous value of a ResultCell before the current transaction, the new value after it, and a convenience flag that indicates that the value has changed.
Since
v2.0.0
ResultCellChange
The ResultCellChange type describes a ResultCell's changes during a transaction.
[changed: boolean, oldCell: ResultCellOrUndefined, newCell: ResultCellOrUndefined]This is returned by the GetResultCellChange function that is provided to every listener when called. This array contains the previous value of a ResultCell before the current transaction, the new value after it, and a convenience flag that indicates that the value has changed.
Since
v2.0.0
ResultCellListener
The ResultCellListener type describes a function that is used to listen to changes to a ResultCell in a query's ResultTable.
(
queries: Queries,
tableId: Id,
rowId: Id,
cellId: Id,
newCell: ResultCell,
oldCell: ResultCell,
getCellChange: GetResultCellChange,
): void| Type | Description | |
|---|---|---|
queries | Queries | A reference to the |
tableId | Id | The |
rowId | Id | |
cellId | Id | The |
newCell | ResultCell | The new value of the |
oldCell | ResultCell | The old value of the |
getCellChange | GetResultCellChange | A function that returns information about any |
| returns | void | This has no return value. |
A ResultCellListener is provided when using the addResultCellListener method. See that method for specific examples.
When called, a ResultCellListener is given a reference to the Queries object, the Id of the ResultTable that changed (which is the same as the query Id), the Id of the ResultRow that changed, and the Id of ResultCell that changed. It is also given the new value of the ResultCell, the old value of the ResultCell, and a GetResultCellChange function that can be used to query ResultCell values before and after the change.
You can create new query definitions within the body of this listener, though obviously be aware of the possible cascading effects of doing so.
Since
v2.0.0
QueryIdsListener
The QueryIdsListener type describes a function that is used to listen to Query definitions being added or removed.
(queries: Queries): void| Type | Description | |
|---|---|---|
queries | Queries | A reference to the |
| returns | void | This has no return value. |
A QueryIdsListener is provided when using the addQueryIdsListener method. See that method for specific examples.
When called, a QueryIdsListener is given a reference to the Queries object.
Since
v2.0.0
Aggregators type aliases
Aggregate
The Aggregate type describes a custom function that takes an array of Cell values and returns an aggregate of them.
(
cells: Cell[],
length: number,
): ResultCell| Type | Description | |
|---|---|---|
cells | Cell[] | The array of |
length | number | The length of the array of |
| returns | ResultCell | The value of the aggregation. |
There are a number of common predefined aggregators, such as for counting, summing, and averaging values. This type is instead used for when you wish to use a more complex aggregation of your own devising.
Since
v2.0.0
AggregateAdd
The AggregateAdd type describes a function that can be used to optimize a custom Aggregate by providing a shortcut for when a single value is added to the input values.
(
current: Cell,
add: Cell,
length: number,
): ResultCellOrUndefined| Type | Description | |
|---|---|---|
current | Cell | The current value of the aggregation. |
add | Cell | The |
length | number | The length of the array of |
| returns | ResultCellOrUndefined | The new value of the aggregation. |
Some aggregation functions do not need to recalculate the aggregation of the whole set when one value changes. For example, when adding a new number to a series, the new sum of the series is the new value added to the previous sum.
If it is not possible to shortcut the aggregation based on just one value being added, return undefined and the aggregation will be completely recalculated.
When possible, if you are providing a custom Aggregate, seek an implementation of an AggregateAdd function that can reduce the complexity cost of growing the input data set.
Since
v2.0.0
AggregateRemove
The AggregateRemove type describes a function that can be used to optimize a custom Aggregate by providing a shortcut for when a single value is removed from the input values.
(
current: Cell,
remove: Cell,
length: number,
): ResultCellOrUndefined| Type | Description | |
|---|---|---|
current | Cell | The current value of the aggregation. |
remove | Cell | The |
length | number | The length of the array of |
| returns | ResultCellOrUndefined | The new value of the aggregation. |
Some aggregation functions do not need to recalculate the aggregation of the whole set when one value changes. For example, when removing a number from a series, the new sum of the series is the new value subtracted from the previous sum.
If it is not possible to shortcut the aggregation based on just one value being removed, return undefined and the aggregation will be completely recalculated. One example might be if you were taking the minimum of the values, and the previous minimum is being removed. The whole of the rest of the list will need to be re-scanned to find a new minimum.
When possible, if you are providing a custom Aggregate, seek an implementation of an AggregateRemove function that can reduce the complexity cost of shrinking the input data set.
Since
v2.0.0
AggregateReplace
The AggregateReplace type describes a function that can be used to optimize a custom Aggregate by providing a shortcut for when a single value in the input values is replaced with another.
(
current: Cell,
add: Cell,
remove: Cell,
length: number,
): ResultCellOrUndefined| Type | Description | |
|---|---|---|
current | Cell | The current value of the aggregation. |
add | Cell | The |
remove | Cell | The |
length | number | The length of the array of |
| returns | ResultCellOrUndefined | The new value of the aggregation. |
Some aggregation functions do not need to recalculate the aggregation of the whole set when one value changes. For example, when replacing a number in a series, the new sum of the series is the previous sum, plus the new value, minus the old value.
If it is not possible to shortcut the aggregation based on just one value changing, return undefined and the aggregation will be completely recalculated.
When possible, if you are providing a custom Aggregate, seek an implementation of an AggregateReplace function that can reduce the complexity cost of changing the input data set in place.
Since
v2.0.0
Callback type aliases
GetTableCell
The GetTableCell type describes a function that takes a Id and returns the Cell value for a particular Row, optionally in a joined Table.
When called with one parameter, this function will return the value of the specified Cell from the query's root Table for the Row being selected or filtered.
(cellId: string): CellOrUndefined| Type | Description | |
|---|---|---|
cellId | string | |
| returns | CellOrUndefined | A |
Since
v2.0.0
When called with two parameters, this function will return the value of the specified Cell from a Table that has been joined in the query, for the Row being selected or filtered.
(
joinedTableId: string,
joinedCellId: string,
): CellOrUndefined| Type | Description | |
|---|---|---|
joinedTableId | string | The |
joinedCellId | string | |
| returns | CellOrUndefined | A |
Since
v2.0.0
A GetTableCell can be provided when setting query definitions, specifically in the Select and Where clauses when you want to create or filter on calculated values. See those methods for specific examples.
Since
v2.0.0
ResultTableCallback
The ResultTableCallback type describes a function that takes a ResultTable's Id and a callback to loop over each ResultRow within it.
(
tableId: Id,
forEachRow: (rowCallback: ResultRowCallback) => void,
): void| Type | Description | |
|---|---|---|
tableId | Id | The |
forEachRow | (rowCallback: ResultRowCallback) => void | A function that will let you iterate over the |
| returns | void | This has no return value. |
A ResultTableCallback is provided when using the forEachResultTable method, so that you can do something based on every ResultTable in the Queries object. See that method for specific examples.
Since
v2.0.0
ResultRowCallback
The ResultRowCallback type describes a function that takes a ResultRow's Id and a callback to loop over each ResultCell within it.
(
rowId: Id,
forEachCell: (cellCallback: ResultCellCallback) => void,
): void| Type | Description | |
|---|---|---|
rowId | Id | |
forEachCell | (cellCallback: ResultCellCallback) => void | A function that will let you iterate over the |
| returns | void | This has no return value. |
A ResultRowCallback is provided when using the forEachResultRow method, so that you can do something based on every ResultRow in a ResultTable. See that method for specific examples.
Since
v2.0.0
ResultCellCallback
The ResultCellCallback type describes a function that takes a ResultCell's Id and its value.
(
cellId: Id,
cell: ResultCell,
): void| Type | Description | |
|---|---|---|
cellId | Id | The |
cell | ResultCell | The value of the |
| returns | void | This has no return value. |
A ResultCellCallback is provided when using the forEachResultCell method, so that you can do something based on every ResultCell in a ResultRow. See that method for specific examples.
Since
v2.0.0
QueryCallback
The QueryCallback type describes a function that takes a query's Id.
(queryId: Id): void| Type | Description | |
|---|---|---|
queryId | Id | The |
| returns | void | This has no return value. |
A QueryCallback is provided when using the forEachQuery method, so that you can do something based on every query in the Queries object. See that method for specific examples.
Since
v2.0.0
Development type aliases
QueriesListenerStats
The QueriesListenerStats type describes the number of listeners registered with the Queries object, and can be used for debugging purposes.
{
table: number;
tableCellIds: number;
rowCount: number;
rowIds: number;
sortedRowIds: number;
row: number;
cellIds: number;
cell: number;
}| Type | Description | |
|---|---|---|
table | number | The number of |
tableCellIds | number | The number of |
rowCount | number | The number of |
rowIds | number | The number of |
sortedRowIds | number | The number of |
row | number | The number of |
cellIds | number | The number of |
cell | number | The number of |
A QueriesListenerStats object is returned from the getListenerStats method.
Since
v2.0.0
checkpoints
The checkpoints module of the TinyBase project provides the ability to create and track checkpoints made to the data in Store objects.
The main entry point to this module is the createCheckpoints function, which returns a new Checkpoints object. From there, you can create new checkpoints, go forwards or backwards to others, and register listeners for when the list of checkpoints change.
Since
v1.0.0
Interfaces
Checkpoints
A Checkpoints object lets you set checkpoints on a Store, and move forward and backward through them to create undo and redo functionality.
Create a Checkpoints object easily with the createCheckpoints function. From there, you can set checkpoints (with the addCheckpoint method), query the checkpoints available (with the getCheckpointIds method), move forward and backward through them (with the goBackward method, goForward method, and goTo method), and add listeners for when the list checkpoints changes (with the addCheckpointIdsListener method).
Checkpoints work for both changes to tabular data and to keyed value data.
Every checkpoint can be given a label which can be used to describe the actions that changed the Store before this checkpoint. This can be useful for interfaces that let users 'Undo [last action]'.
Example
This example shows a simple lifecycle of a Checkpoints object: from creation, to adding a checkpoint, getting the list of available checkpoints, and then registering and removing a listener for them.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore()
.setTables({pets: {fido: {sold: false}}})
.setValue('open', true);
const checkpoints = createCheckpoints(store);
checkpoints.setSize(200);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]
checkpoints.goBackward();
console.log(store.getCell('pets', 'fido', 'sold'));
// -> false
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', ['1']]
store.setValue('open', false);
checkpoints.addCheckpoint('closed');
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '2', []]
checkpoints.goBackward();
console.log(store.getValue('open'));
// -> true
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', ['2']]
const listenerId = checkpoints.addCheckpointIdsListener(() => {
console.log(checkpoints.getCheckpointIds());
});
store.setCell('pets', 'fido', 'species', 'dog');
// -> [['0'], undefined, []]
checkpoints.addCheckpoint();
// -> [['0'], '3', []]
// Previous redo of checkpoints '1' and '2' are now not possible.
checkpoints.delListener(listenerId);
See also
- Using Checkpoints guide
- Todo App demos
- Drawing demo
Since
v1.0.0
Getter methods
getStore
The getStore method returns a reference to the underlying Store that is backing this Checkpoints object.
getStore(): StoreExample
This example creates a Checkpoints object against a newly-created Store and then gets its reference in order to update its data and set a checkpoint.
import {createCheckpoints, createStore} from 'tinybase';
const checkpoints = createCheckpoints(createStore());
checkpoints.getStore().setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint();
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]
Since
v1.0.0
getCheckpoint
The getCheckpoint method fetches the label for a checkpoint, if it had been provided at the time of the addCheckpoint method or set subsequently with the setCheckpoint method.
getCheckpoint(checkpointId: string): undefined | string| Type | Description | |
|---|---|---|
checkpointId | string | The |
| returns | undefined | string | A string label for the requested checkpoint, an empty string if it was never set, or |
If the checkpoint has had no label provided, this method will return an empty string.
Examples
This example creates a Store, adds a Checkpoints object, and sets a checkpoint with a label, before retrieving it again.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
store.setCell('pets', 'fido', 'sold', true);
console.log(checkpoints.addCheckpoint('sale'));
// -> '1'
console.log(checkpoints.getCheckpoint('1'));
// -> 'sale'
This example creates a Store, adds a Checkpoints object, and sets a checkpoint without a label, setting it subsequently. A non-existent checkpoint return an undefined label.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint();
console.log(checkpoints.getCheckpoint('1'));
// -> ''
checkpoints.setCheckpoint('1', 'sold');
console.log(checkpoints.getCheckpoint('1'));
// -> 'sold'
console.log(checkpoints.getCheckpoint('2'));
// -> undefined
Since
v1.0.0
getCheckpointIds
The getCheckpointIds method returns an array of the checkpoint Ids being managed by this Checkpoints object.
getCheckpointIds(): CheckpointIds| returns | CheckpointIds | A |
|---|
The returned CheckpointIds array contains 'backward' checkpoint Ids, the current checkpoint Id (if present), and the 'forward' checkpointIds. Together, these are sufficient to understand the state of the Checkpoints object and what movement is possible backward or forward through the checkpoint stack.
Example
This example creates a Store, adds a Checkpoints object, and then gets the Ids of the checkpoints as it sets them and moves around the stack.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]
checkpoints.goBackward();
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', ['1']]
checkpoints.goForward();
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]
Since
v1.0.0
hasCheckpoint
The hasCheckpoint method returns a boolean indicating whether a given Checkpoint exists in the Checkpoints object.
hasCheckpoint(checkpointId: string): boolean| Type | Description | |
|---|---|---|
checkpointId | string | The |
| returns | boolean | Whether a Checkpoint with that |
Example
This example shows two simple Checkpoint existence checks.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
console.log(checkpoints.hasCheckpoint('0'));
// -> true
console.log(checkpoints.hasCheckpoint('1'));
// -> false
Since
v1.0.0
Setter methods
addCheckpoint
The addCheckpoint method records a checkpoint of the Store into the Checkpoints object that can be reverted to in the future.
addCheckpoint(label?: string): string| Type | Description | |
|---|---|---|
label? | string | An optional label to describe the actions leading up to this checkpoint. |
| returns | string | The |
If no changes have been made to the Store since the last time a checkpoint was made, this method will have no effect.
The optional label parameter can be used to describe the actions that changed the Store before this checkpoint. This can be useful for interfaces that let users 'Undo [last action]'.
Example
This example creates a Store, adds a Checkpoints object, and adds two checkpoints, one with a label.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
store.setCell('pets', 'fido', 'species', 'dog');
const checkpointId1 = checkpoints.addCheckpoint();
console.log(checkpointId1);
// -> '1'
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(checkpoints.getCheckpointIds());
// -> [['0', '1'], '2', []]
console.log(checkpoints.getCheckpoint('2'));
// -> 'sale'
Since
v1.0.0
setCheckpoint
The setCheckpoint method updates the label for a checkpoint in the Checkpoints object after it has been created.
setCheckpoint(
checkpointId: string,
label: string,
): Checkpoints| Type | Description | |
|---|---|---|
checkpointId | string | The |
label | string | A label to describe the actions leading up to this checkpoint or left undefined if you want to clear the current label. |
| returns | Checkpoints | A reference to the |
The label parameter can be used to describe the actions that changed the Store before the given checkpoint. This can be useful for interfaces that let users 'Undo [last action]'.
Generally you will provide the label parameter when the addCheckpoint method is called. Use this setCheckpoint method only when you need to change the label at a later point.
You cannot add a label to a checkpoint that does not yet exist.
Example
This example creates a Store, adds a Checkpoints object, and sets two checkpoints, one with a label, which are both then re-labelled.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint();
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(checkpoints.getCheckpoint('1'));
// -> ''
console.log(checkpoints.getCheckpoint('2'));
// -> 'sale'
checkpoints.setCheckpoint('1', 'identified');
checkpoints.setCheckpoint('2', '');
console.log(checkpoints.getCheckpoint('1'));
// -> 'identified'
console.log(checkpoints.getCheckpoint('2'));
// -> ''
checkpoints.setCheckpoint('3', 'unknown');
console.log(checkpoints.getCheckpoint('3'));
// -> undefined
Since
v1.0.0
Listener methods
addCheckpointIdsListener
The addCheckpointIdsListener method registers a listener function with the Checkpoints object that will be called whenever its set of checkpoints changes.
addCheckpointIdsListener(listener: CheckpointIdsListener): string| Type | Description | |
|---|---|---|
listener | CheckpointIdsListener | The function that will be called whenever the checkpoints change. |
| returns | string | A unique |
The provided listener is a CheckpointIdsListener function, and will be called with a reference to the Checkpoints object.
Example
This example creates a Store, a Checkpoints object, and then registers a listener that responds to any changes to the checkpoints.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
const listenerId = checkpoints.addCheckpointIdsListener(() => {
console.log('Checkpoint Ids changed');
console.log(checkpoints.getCheckpointIds());
});
store.setCell('pets', 'fido', 'species', 'dog');
// -> 'Checkpoint Ids changed'
// -> [['0'], undefined, []]
checkpoints.addCheckpoint();
// -> 'Checkpoint Ids changed'
// -> [['0'], '1', []]
checkpoints.goBackward();
// -> 'Checkpoint Ids changed'
// -> [[], '0', ['1']]
checkpoints.goForward();
// -> 'Checkpoint Ids changed'
// -> [['0'], '1', []]
checkpoints.delListener(listenerId);
Since
v1.0.0
addCheckpointListener
The addCheckpointListener method registers a listener function with the Checkpoints object that will be called whenever the label of a checkpoint changes.
addCheckpointListener(
checkpointId: IdOrNull,
listener: CheckpointListener,
): string| Type | Description | |
|---|---|---|
checkpointId | IdOrNull | The |
listener | CheckpointListener | The function that will be called whenever the checkpoint label changes. |
| returns | string | A unique |
You can either listen to a single checkpoint label (by specifying the checkpoint Id as the method's first parameter), or changes to any checkpoint label (by providing a null wildcard).
The provided listener is a CheckpointListener function, and will be called with a reference to the Checkpoints object, and the Id of the checkpoint whose label changed.
Example
This example creates a Store, a Checkpoints object, and then registers a listener that responds to any changes to a specific checkpoint label, including when the checkpoint no longer exists.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
const listenerId = checkpoints.addCheckpointListener('1', () => {
console.log('Checkpoint 1 label changed');
console.log(checkpoints.getCheckpoint('1'));
});
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
// -> 'Checkpoint 1 label changed'
// -> 'sale'
checkpoints.setCheckpoint('1', 'sold');
// -> 'Checkpoint 1 label changed'
// -> 'sold'
checkpoints.setCheckpoint('1', 'sold');
// The listener is not called when the label does not change.
checkpoints.goTo('0');
store.setCell('pets', 'fido', 'sold', false);
// -> 'Checkpoint 1 label changed'
// -> undefined
// The checkpoint no longer exists.
checkpoints.delListener(listenerId);
Since
v1.0.0
delListener
The delListener method removes a listener that was previously added to the Checkpoints object.
delListener(listenerId: string): Checkpoints| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | Checkpoints | A reference to the |
Use the Id returned by the addCheckpointIdsListener method. Note that the Checkpoints object may re-use this Id for future listeners added to it.
Example
This example creates a Store, a Checkpoints object, registers a listener, and then removes it.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
const listenerId = checkpoints.addCheckpointIdsListener(() => {
console.log('checkpoints changed');
});
store.setCell('pets', 'fido', 'species', 'dog');
// -> 'checkpoints changed'
checkpoints.addCheckpoint();
// -> 'checkpoints changed'
checkpoints.delListener(listenerId);
store.setCell('pets', 'fido', 'sold', 'true');
// -> undefined
// The listener is not called.
Since
v1.0.0
Configuration methods
setSize
The setSize method lets you specify how many checkpoints the Checkpoints object will store.
setSize(size: number): Checkpoints| Type | Description | |
|---|---|---|
size | number | The number of checkpoints that this |
| returns | Checkpoints | A reference to the |
If you set more checkpoints than this size, the oldest checkpoints will be pruned to make room for more recent ones.
The default size for a newly-created Checkpoints object is 100.
Example
This example creates a Store, adds a Checkpoints object, reduces the size of the Checkpoints object dramatically and then creates more than that number of checkpoints to demonstrate the oldest being pruned.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {views: 0}}});
const checkpoints = createCheckpoints(store);
checkpoints.setSize(2);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
store.setCell('pets', 'fido', 'views', 1);
checkpoints.addCheckpoint();
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]
store.setCell('pets', 'fido', 'views', 2);
checkpoints.addCheckpoint();
console.log(checkpoints.getCheckpointIds());
// -> [['0', '1'], '2', []]
store.setCell('pets', 'fido', 'views', 3);
checkpoints.addCheckpoint();
console.log(checkpoints.getCheckpointIds());
// -> [['1', '2'], '3', []]
Since
v1.0.0
Iterator methods
forEachCheckpoint
The forEachCheckpoint method takes a function that it will then call for each Checkpoint in a specified Checkpoints object.
forEachCheckpoint(checkpointCallback: CheckpointCallback): void| Type | Description | |
|---|---|---|
checkpointCallback | CheckpointCallback | The function that should be called for every Checkpoint. |
| returns | void | This has no return value. |
This method is useful for iterating over the structure of the Checkpoints object in a functional style. The checkpointCallback parameter is a CheckpointCallback function that will be called with the Id of each Checkpoint.
Example
This example iterates over each Checkpoint in a Checkpoints object.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
checkpoints.forEachCheckpoint((checkpointId, label) => {
console.log(`${checkpointId}:${label}`);
});
// -> '0:'
// -> '1:sale'
Since
v1.0.0
Lifecycle methods
clear
The clear method resets this Checkpoints object to its initial state, removing all the checkpoints it has been managing.
clear(): Checkpoints| returns | Checkpoints | A reference to the |
|---|
Obviously this method should be used with caution as it destroys the ability to undo or redo recent changes to the Store (though of course the Store itself is not reset by this method).
This method can be useful when a Store is being loaded via a Persister asynchronously after the Checkpoints object has been attached, and you don't want users to be able to undo the initial load of the data. In this case you could call the clear method immediately after the initial load so that that is the baseline from which all subsequent changes are tracked.
Example
This example creates a Store, a Checkpoints object, adds a listener, makes a change and then clears the checkpoints.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
const listenerId = checkpoints.addCheckpointIdsListener(() => {
console.log('checkpoints changed');
});
store.setCell('pets', 'fido', 'color', 'brown');
// -> 'checkpoints changed'
checkpoints.addCheckpoint();
// -> 'checkpoints changed'
store.setCell('pets', 'fido', 'sold', true);
// -> 'checkpoints changed'
checkpoints.addCheckpoint();
// -> 'checkpoints changed'
console.log(store.getTables());
// -> {pets: {fido: {sold: true, color: 'brown'}}}
console.log(checkpoints.getCheckpointIds());
// -> [['0', '1'], '2', []]
checkpoints.clear();
// -> 'checkpoints changed'
console.log(store.getTables());
// -> {pets: {fido: {sold: true, color: 'brown'}}}
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
checkpoints.delListener(listenerId);
Since
v1.0.0
clearForward
The clearForward method resets just the 'redo' checkpoints it has been managing.
clearForward(): Checkpoints| returns | Checkpoints | A reference to the |
|---|
Obviously this method should be used with caution as it destroys the ability to redo recent changes to the Store (though of course the Store itself is not reset by this method).
This method can be useful when you want to prohibit a user from redoing changes they have undone. The 'backward' redo stack, and current checkpoint are not affected.
Example
This example creates a Store, a Checkpoints object, adds a listener, makes a change and then clears the forward checkpoints.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
const listenerId = checkpoints.addCheckpointIdsListener(() => {
console.log('checkpoints changed');
});
store.setCell('pets', 'fido', 'color', 'brown');
// -> 'checkpoints changed'
checkpoints.addCheckpoint();
// -> 'checkpoints changed'
store.setCell('pets', 'fido', 'sold', true);
// -> 'checkpoints changed'
checkpoints.addCheckpoint();
// -> 'checkpoints changed'
checkpoints.goBackward();
// -> 'checkpoints changed'
console.log(store.getTables());
// -> {pets: {fido: {color: 'brown', sold: false}}}
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', ['2']]
checkpoints.clearForward();
// -> 'checkpoints changed'
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]
checkpoints.delListener(listenerId);
Since
v4.5.3
destroy
The destroy method should be called when this Checkpoints object is no longer used.
destroy(): voidThis guarantees that all of the listeners that the object registered with the underlying Store are removed and it can be correctly garbage collected.
Example
This example creates a Store, adds a Checkpoints object (that registers a CellListener with the underlying Store), and then destroys it again, removing the listener.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
console.log(store.getListenerStats().cell);
// -> 1
checkpoints.destroy();
console.log(store.getListenerStats().cell);
// -> 0
Since
v1.0.0
Movement methods
goBackward
The goBackward method moves the state of the underlying Store back to the previous checkpoint, effectively performing an 'undo' on the Store data.
goBackward(): Checkpoints| returns | Checkpoints | A reference to the |
|---|
If there is no previous checkpoint to return to, this method has no effect.
Example
This example creates a Store, a Checkpoints object, makes a change and then goes backward to the state of the Store before the change.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]
checkpoints.goBackward();
console.log(store.getCell('pets', 'fido', 'sold'));
// -> false
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', ['1']]
Since
v1.0.0
goForward
The goForward method moves the state of the underlying Store forwards to a future checkpoint, effectively performing an 'redo' on the Store data.
goForward(): Checkpoints| returns | Checkpoints | A reference to the |
|---|
If there is no future checkpoint to return to, this method has no effect.
Note that if you have previously used the goBackward method to undo changes, the forwards 'redo' stack will only exist while you do not make changes to the Store. In general the goForward method is expected to be used to redo changes that were just undone.
Examples
This example creates a Store, a Checkpoints object, makes a change and then goes backward to the state of the Store before the change. It then goes forward again to restore the state with the changes.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]
checkpoints.goBackward();
console.log(store.getCell('pets', 'fido', 'sold'));
// -> false
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', ['1']]
checkpoints.goForward();
console.log(store.getCell('pets', 'fido', 'sold'));
// -> true
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]
This example creates a Store, a Checkpoints object, makes a change and then goes backward to the state of the Store before the change. It makes a new change, the redo stack disappears, and then the attempt to forward again has no effect.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]
checkpoints.goBackward();
console.log(store.getCell('pets', 'fido', 'sold'));
// -> false
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', ['1']]
store.setCell('pets', 'fido', 'color', 'brown');
console.log(checkpoints.getCheckpointIds());
// -> [['0'], undefined, []]
checkpoints.goForward();
console.log(store.getCell('pets', 'fido', 'sold'));
// -> false
console.log(checkpoints.getCheckpointIds());
// -> [['0'], undefined, []]
// The original change cannot be redone.
Since
v1.0.0
goTo
The goTo method moves the state of the underlying Store backwards or forwards to a specified checkpoint.
goTo(checkpointId: string): Checkpoints| Type | Description | |
|---|---|---|
checkpointId | string | The |
| returns | Checkpoints | A reference to the |
If there is no checkpoint with the Id specified, this method has no effect.
Example
This example creates a Store, a Checkpoints object, makes two changes and then goes directly to the state of the Store before the two changes. It then goes forward again one change, also using the goTo method. Finally it tries to go to a checkpoint that does not exist.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
store.setCell('pets', 'fido', 'color', 'brown');
checkpoints.addCheckpoint('identification');
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(checkpoints.getCheckpointIds());
// -> [['0', '1'], '2', []]
checkpoints.goTo('0');
console.log(store.getTables());
// -> {pets: {fido: {sold: false}}}
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', ['1', '2']]
checkpoints.goTo('1');
console.log(store.getTables());
// -> {pets: {fido: {sold: false, color: 'brown'}}}
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', ['2']]
checkpoints.goTo('3');
console.log(store.getTables());
// -> {pets: {fido: {sold: false, color: 'brown'}}}
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', ['2']]
Since
v1.0.0
Development methods
getListenerStats
The getListenerStats method provides a set of statistics about the listeners registered with the Checkpoints object, and is used for debugging purposes.
getListenerStats(): CheckpointsListenerStats| returns | CheckpointsListenerStats | A |
|---|
The CheckpointsListenerStats object contains a breakdown of the different types of listener.
The method is intended to be used during development to ensure your application is not leaking listener registrations, for example.
Example
This example gets the listener statistics of a Checkpoints object.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore();
const checkpoints = createCheckpoints(store);
checkpoints.addCheckpointIdsListener(() => {
console.log('Checkpoint Ids changed');
});
checkpoints.addCheckpointListener(null, () => {
console.log('Checkpoint label changed');
});
console.log(checkpoints.getListenerStats());
// -> {checkpointIds: 1, checkpoint: 1}
Since
v1.0.0
Functions
createCheckpoints
The createCheckpoints function creates a Checkpoints object, and is the main entry point into the checkpoints module.
createCheckpoints(store: Store): Checkpoints| Type | Description | |
|---|---|---|
store | Store | The |
| returns | Checkpoints | A reference to the new |
A given Store can only have one Checkpoints object associated with it. If you call this function twice on the same Store, your second call will return a reference to the Checkpoints object created by the first.
Examples
This example creates a Checkpoints object.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore();
const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
This example creates a Checkpoints object, and calls the method a second time for the same Store to return the same object.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore();
const checkpoints1 = createCheckpoints(store);
const checkpoints2 = createCheckpoints(store);
console.log(checkpoints1 === checkpoints2);
// -> true
Since
v1.0.0
Type Aliases
Listener type aliases
CheckpointIdsListener
The CheckpointIdsListener type describes a function that is used to listen to changes to the checkpoint Ids in a Checkpoints object.
(checkpoints: Checkpoints): void| Type | Description | |
|---|---|---|
checkpoints | Checkpoints | A reference to the |
| returns | void | This has no return value. |
A CheckpointIdsListener is provided when using the addCheckpointIdsListener method. See that method for specific examples.
When called, a CheckpointIdsListener is given a reference to the Checkpoints object.
Since
v1.0.0
CheckpointListener
The CheckpointListener type describes a function that is used to listen to changes to a checkpoint's label in a Checkpoints object.
(
checkpoints: Checkpoints,
checkpointId: Id,
): void| Type | Description | |
|---|---|---|
checkpoints | Checkpoints | A reference to the |
checkpointId | Id | The |
| returns | void | This has no return value. |
A CheckpointListener is provided when using the addCheckpointListener method. See that method for specific examples.
When called, a CheckpointListener is given a reference to the Checkpoints object, and the Id of the checkpoint whose label changed.
Since
v1.0.0
Callback type aliases
CheckpointCallback
The CheckpointCallback type describes a function that takes a Checkpoint's Id.
(
checkpointId: Id,
label?: string,
): void| Type | Description | |
|---|---|---|
checkpointId | Id | The |
label? | string | |
| returns | void | This has no return value. |
A CheckpointCallback is provided when using the forEachCheckpoint method, so that you can do something based on every Checkpoint in the Checkpoints object. See that method for specific examples.
Since
v1.0.0
Identity type aliases
CheckpointIds
The CheckpointIds type is a representation of the list of checkpoint Ids stored in a Checkpoints object.
[Ids, Id | undefined, Ids]There are three parts to a CheckpointsIds array:
- The 'backward' checkpoint
Idsthat can be rolled backward to (in other words, the checkpoints in the undo stack for thisStore). They are in chronological order with the oldest checkpoint at the start of the array. - The current checkpoint
Idof theStore's state, orundefinedif the current state has not been checkpointed. - The 'forward' checkpoint
Idsthat can be rolled forward to (in other words, the checkpoints in the redo stack for thisStore). They are in chronological order with the newest checkpoint at the end of the array.
Since
v1.0.0
Development type aliases
CheckpointsListenerStats
The CheckpointsListenerStats type describes the number of listeners registered with the Checkpoints object, and can be used for debugging purposes.
{
checkpointIds: number;
checkpoint: number;
}| Type | Description | |
|---|---|---|
checkpointIds | number | The number of |
checkpoint | number | The number of |
A CheckpointsListenerStats object is returned from the getListenerStats method.
Since
v1.0.0
common
The common module of the TinyBase project provides a small collection of common types used across other modules.
Since
v1.0.0
Functions
Convenience functions
defaultSorter
The defaultSorter function is provided as a convenience to sort keys alphanumerically, and can be provided to the sliceIdSorter and rowIdSorter parameters of the setIndexDefinition method in the indexes module, for example.
defaultSorter(
sortKey1: SortKey,
sortKey2: SortKey,
): number| Type | Description | |
|---|---|---|
sortKey1 | SortKey | The first item of the pair to compare. |
sortKey2 | SortKey | The second item of the pair to compare. |
| returns | number | A number indicating how to sort the pair. |
Examples
This example creates an Indexes object.
import {createIndexes, createStore} from 'tinybase';
const store = createStore();
const indexes = createIndexes(store);
console.log(indexes.getIndexIds());
// -> []
This example creates a Store, creates an Indexes object, and defines an Index based on the first letter of the pets' names. The Slice Ids (and Row Ids within them) are alphabetically sorted using the defaultSorter function.
import {createIndexes, createStore, defaultSorter} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition(
'byFirst', // indexId
'pets', // tableId
(_, rowId) => rowId[0], // each Row's Slice Id
(_, rowId) => rowId, // each Row's sort key
defaultSorter, // sort Slice Ids
defaultSorter, // sort Row Ids by sort key
);
console.log(indexes.getSliceIds('byFirst'));
// -> ['c', 'f']
console.log(indexes.getSliceRowIds('byFirst', 'f'));
// -> ['felix', 'fido']
Since
v1.0.0
getUniqueId
The getUniqueId function returns a unique string of a given length.
getUniqueId(length?: number): Id| Type | Description | |
|---|---|---|
length? | number | The desired length of the unique |
| returns | Id | A unique |
This is used internally by TinyBase for the synchronizer protocol and for unique MergeableStore identifiers. But it is useful enough for it to be publicly exposed for purposes such as identifying shared collaboration rooms, or creating other Ids that need to be unique.
The string may contain numbers, lower or upper case letters, or the '-' or '_' characters. This makes them URL-safe, and means they can be identified with a regex like /[-_0-9A-Za-z]+/.
This function prefers to use the crypto module to generate random numbers, but where that is not available (such as in React Native), a Math.random implementation is used. Whilst that may not be cryptographically sound, it should suffice for most TinyBase-related purposes.
Example
This example creates two 8 character long Ids and compares them.
import {getUniqueId} from 'tinybase';
const id1 = getUniqueId(8);
const id2 = getUniqueId(8);
console.log(id1.length);
// -> 8
console.log(id2.length);
// -> 8
console.log(id1 == id2);
// -> false
Since
v5.0.0
Hash functions
getTableInTablesHash
The getTableInTablesHash function returns a hash for a single Table in a Store, based on its Id and hash (in turn produced from its Rows).
getTableInTablesHash(
tableId: string,
tableHash: number,
): HashExample
This example gets the hash of a Table and its Id.
import {getTableInTablesHash} from 'tinybase';
const tableId = 'pets';
const tableHash = 4262151841; // hash of its contents
console.log(getTableInTablesHash(tableId, tableHash));
// -> 278115833
Since
v6.2.0
getTablesHash
The getTablesHash function returns a hash for the tabular part of a Store, based on each Table Id and hash.
getTablesHash(tableHashes: {[tableId: Id]: Hash}): HashExample
This example gets the hash of the tabular part of a Store.
import {getTablesHash} from 'tinybase';
const tableHashes = {
pets: 4262151841, // hash of its contents
};
console.log(getTablesHash(tableHashes));
// -> 278115833
Since
v6.2.0
getRowInTableHash
The getRowInTableHash function returns a hash for a single Row in a Table, based on its Id and hash (in turn produced from its Cells).
getRowInTableHash(
rowId: string,
rowHash: number,
): HashExample
This example gets the hash of a Row and its Id.
import {getRowInTableHash} from 'tinybase';
const rowId = 'fido';
const rowHash = 1810444343; // hash of its contents
console.log(getRowInTableHash(rowId, rowHash));
// -> 4262151841
Since
v6.2.0
getTableHash
The getTableHash function returns a hash for a single Table in a Store, based on each Row Id and hash.
getTableHash(rowHashes: {[rowId: Id]: Hash}): HashExample
This example gets the hash of a Table.
import {getTableHash} from 'tinybase';
const rowHashes = {
fido: 1810444343, // hash of its contents
};
console.log(getTableHash(rowHashes));
// -> 4262151841
Since
v6.2.0
getCellInRowHash
The getCellInRowHash function returns a hash for a single Cell in a Row, based on its Id and hash (in turn produced from its value).
getCellInRowHash(
cellId: string,
cellHash: number,
): HashExample
This example gets the hash of a Cell and its Id.
import {getCellInRowHash} from 'tinybase';
const cellId = 'species';
const cellHash = '3002200796'; // hash of 'dog' and '03E3B------mmxrx'
console.log(getCellInRowHash(cellId, cellHash));
// -> 3777304796
Since
v6.2.0
getRowHash
The getRowHash function returns a hash for a single Row in a Table, based on each Cell Id and hash.
getRowHash(cellHashes: {[cellId: Id]: Hash}): HashExample
This example gets the hash of a Row.
import {getRowHash} from 'tinybase';
const cellHashes = {
species: 3002200796, // hash of 'dog' and '03E3B------mmxrx'
};
console.log(getRowHash(cellHashes));
// -> 1810444343
Since
v6.2.0
getCellHash
The getCellHash function returns a hash for a single Cell, based on its value and the HLC of the Cell.
getCellHash(
cell: CellOrUndefined,
cellHlc: string,
): Hash| Type | Description | |
|---|---|---|
cell | CellOrUndefined | The |
cellHlc | string | The HLC of the |
| returns | Hash | A hash of the |
Example
This example gets the hash of a Cell and its HLC.
import {getCellHash} from 'tinybase';
const cell = 'dog';
const cellHlc = '03E3B------mmxrx';
console.log(getCellHash(cell, cellHlc));
// -> 3002200796
Since
v6.2.0
getValueInValuesHash
The getValueInValuesHash function returns a hash for a single Value in a Values object, based on its Id and hash (in turn produced from its value).
getValueInValuesHash(
valueId: string,
valueHash: number,
): HashExample
This example gets the hash of a Value and its Id.
import {getValueInValuesHash} from 'tinybase';
const valueId = 'meaningOfLife';
const valueHash = 312420374; // hash of 42 and '03E3B------mmxrx'
console.log(getValueInValuesHash(valueId, valueHash));
// -> 330198963
Since
v6.2.0
getValuesHash
The getValuesHash function returns a hash for a Values object, based on each Value Id and hash.
getValuesHash(valueHashes: {[valueId: Id]: Hash}): HashExample
This example gets the hash of a Values object.
import {getValuesHash} from 'tinybase';
const valueHashes = {
meaningOfLife: 312420374, // hash of 42 and '03E3B------mmxrx'
};
console.log(getValuesHash(valueHashes));
// -> 4229195646
Since
v6.2.0
getValueHash
The getValueHash function returns a hash for a single Value, based on its value and the HLC of the Value.
getValueHash(
value: ValueOrUndefined,
valueHlc: string,
): Hash| Type | Description | |
|---|---|---|
value | ValueOrUndefined | The |
valueHlc | string | The HLC of the |
| returns | Hash | A hash of the |
Example
This example gets the hash of a Value and its HLC.
import {getValueHash} from 'tinybase';
const value = 42;
const valueHlc = '03E3B------mmxrx';
console.log(getValueHash(value, valueHlc));
// -> 312420374
Since
v6.2.0
addOrRemoveHash
The addOrRemoveHash function combines two hashes together, which, because it is a simple alias for bitwise XOR, serves both as addition and removal of one hash from the other.
addOrRemoveHash(
hash1: number,
hash2: number,
): Hash| Type | Description | |
|---|---|---|
hash1 | number | A first hash. |
hash2 | number | A second hash to add or remove from the first. |
| returns | Hash | The resulting hash of the two hashes added to or removed from each other. |
This is used internally within TinyBase to collate hashes of objects, such as producing a hash for a Table which is composed of the hashes of its Rows.
Example
This example adds two hashes together.
import {addOrRemoveHash} from 'tinybase';
const hash1 = 123456789;
const hash2 = 987654321;
console.log(addOrRemoveHash(hash1, hash2));
// -> 1032168868
console.log(addOrRemoveHash(1032168868, hash1));
// -> 987654321
console.log(addOrRemoveHash(1032168868, hash2));
// -> 123456789
Since
v6.2.0
getHash
The getHash function returns a deterministic hash of a string, which can be used to quickly compare the content of two entities.
getHash(string: string): Hash| Type | Description | |
|---|---|---|
string | string | The string to hash. |
| returns | Hash | A hash of the string. |
This is used internally within TinyBase (for example in the mergeable-store module) to quickly compare the content of two objects, but is also useful for applications that need to represent strings in a deterministic (though not cryptographically safe) way.
The hash uses the Fowler–Noll–Vo hash approach, producing a JavaScript number. It is not guaranteed to be unique by any means, but small changes in input generally produce large changes in output, so it should be sufficient for most purposes.
Example
This example gets the hash of two similar strings.
import {getHash} from 'tinybase';
console.log(getHash('Hello, world!'));
// -> 3985698964
console.log(getHash('Hello, world?'));
// -> 3549480870
Since
v6.2.0
Stamps functions
getHlcFunctions
The getHlcFunctions function returns a set of utility functions for working with the TinyBase Hybrid Logical Clock (HLC).
getHlcFunctions(
uniqueId?: string,
getNow?: GetNow,
): [getNextHlc: () => Hlc, seenHlc: (remoteHlc: Hlc) => void, encodeHlc: (logicalTime: number, counter: number, clientId?: Id) => Hlc, decodeHlc: (hlc: Hlc) => [logicalTime: number, counter: number, clientId: Id], getLastLogicalTime: () => number, getLastCounter: () => number, getClientId: () => Id]| Type | Description | |
|---|---|---|
uniqueId? | string | An optional unique |
getNow? | GetNow | An optional function that generates millisecond timestamps, defaulting to |
| returns | [getNextHlc: () => Hlc, seenHlc: (remoteHlc: Hlc) => void, encodeHlc: (logicalTime: number, counter: number, clientId?: Id) => Hlc, decodeHlc: (hlc: Hlc) => [logicalTime: number, counter: number, clientId: Id], getLastLogicalTime: () => number, getLastCounter: () => number, getClientId: () => Id] | An array of seven stateful functions as described above. |
An HLC is a sortable 16 character string that encodes a timestamp, a counter, and the hash of a unique client identifier. You should provide that unique client identifier as the uniqueId parameter to the function. Otherwise it will be defaulted and the client suffix of the HLCs the getNextHlc function generates will be non-deterministic.
You can also provide a getNow function that returns the current time in milliseconds, which is useful for testing purposes where fully deterministic HLCs are required, rather than the current time.
The stateful functions returned by this function are as follows.
getNextHlc: a function that returns the next HLC for this client based on the time and any other HLC values from other clients that have been seen.seenHlc: a function that takes an HLC and updates the internal state of the functions to ensure that the next HLC returned bygetNextHlcis greater than the given seen HLC.encodeHlc: a function that takes a timestamp, a counter (and optionally a differentclientId) and encodes the them into an HLC string.decodeHlc: a function that takes an HLC and returns an array containing the logical time, counter, and clientIdparts.getLastLogicalTime: a function that returns the last logical time either generated or seen by this client.getLastCounter: a function that returns the last counter either generated or seen by this client.getClientId: a function that returns the clientIdfor this client; either uniquely generated or derived from theuniqueIdparameter.
Example
This example gets the HLC functions (for a given client Id and a fixed time; both for illustrative purposes), and then uses them:
import {getHlcFunctions} from 'tinybase';
const [
getNextHlc,
seenHlc,
encodeHlc,
decodeHlc,
getLastLogicalTime,
getLastCounter,
getClientId,
] = getHlcFunctions('client1', () => 73267200000); // This client is in 1972.
// Generate an HLC based on the fixed time and the client Id.
console.log(getNextHlc());
// -> '03E3B------mmxrx'
// Generate the next HLC. The time has not changed, so the counter does.
console.log(getNextHlc());
// -> '03E3B-----0mmxrx'
// Another client thinks it is 1973.
seenHlc('0WakTk-----jmx_3');
// Generate the next HLC.
// What is the state for the current client?
console.log(getLastLogicalTime());
// -> 104803200000
console.log(getLastCounter());
// -> 0
console.log(getClientId());
// -> 'mmxrx'
// Encode an arbitrary HLC.
console.log(encodeHlc(73267203600, 7, 'client3'));
// -> '03E3BsF---6kmxfM'
// Decode it again.
console.log(decodeHlc('03E3BsF---6kmxfM'));
// -> [73267203600, 7, 'kmxfM']
Since
v6.2.0
Type Aliases
Callback type aliases
Callback
The Callback type represents a function that is used as a callback and which does not take a parameter.
(): voidSince
v1.0.0
ParameterizedCallback
The ParameterizedCallback type represents a generic function that will take an optional parameter - such as the handler of a DOM event.
(parameter?: Parameter): void| Type | Description | |
|---|---|---|
parameter? | Parameter | |
| returns | void | This has no return value. |
Since
v1.0.0
General type aliases
Json
The Json type is a simple alias for a string, but is used to indicate that the string should be considered to be a JSON serialization of an object.
stringSince
v1.0.0
Identity type aliases
Id
The Id type is a simple alias for a string, but is used to indicate that the string should be considered to be the key of an object (such as a Row Id string used in a Table).
stringSince
v1.0.0
IdOrNull
The IdOrNull type is a simple alias for the union of a string or null value, where the string should be considered to be the key of an objects (such as a Row Id string used in a Table), and typically null indicates a wildcard - such as when used in the Store addRowListener method.
Id | nullSince
v1.0.0
Ids
The Ids type is a simple alias for an array of strings, but is used to indicate that the strings should be considered to be the keys of objects (such as the Row Id strings used in a Table).
Id[]Since
v1.0.0
Parameter type aliases
SortKey
The SortKey type represents a value that can be used by a sort function.
string | number | booleanSince
v1.0.0
Stamps type aliases
GetNow
The GetNow type is used to represent a function that returns the current time in milliseconds.
(): number| returns | number |
|---|
This is used internally within the mergeable-store module, but is used for the createMergeableStore function's second optional argument to allow applications to override the clock used to generate timestamps.
Since
v6.2.0
Hash
The Hash type is used within TinyBase (for example in the mergeable-store module) to quickly compare the content of two objects.
numberThis is simply an alias for a JavaScript number.
This type is mostly utilized internally within TinyBase itself and is generally assumed to be opaque to applications that use it.
Since
v6.2.0
Hlc
The Hlc type is a string that represents a Hybrid Logical Clock (HLC) value.
stringHLCs are used to provide a globally unique timestamp that can be used to order events across distributed systems. The Hlc type in TinyBase is a sortable 16 character string that encodes a timestamp, a counter, and the hash of a unique client identifier.
- 42 bits (7 chars) for the time in milliseconds (~139 years).
- 24 bits (4 chars) for the counter (~16 million).
- 30 bits (5 chars) for the hash of unique client id (~1 billion).
Since
v6.2.0
persisters
The persisters module of the TinyBase project provides a simple framework for saving and loading Store and MergeableStore data, to and from different destinations, or underlying storage types.
Many entry points are provided (in separately installed modules), each of which returns different types of Persister that can load and save a Store. Between them, these allow you to store your TinyBase data locally, remotely, to a Durable Object, to SQLite and PostgreSQL databases, and across synchronization boundaries with CRDT frameworks.
(*) Note that SQLite- and PostgreSQL-based Persisters can currently only persist MergeableStore data when used with the JSON-based DpcJson mode, and not when using the DpcTabular mode.
Since persistence requirements can be different for every app, the createCustomPersister function in this module can also be used to easily create a fully customized way to save and load Store data.
Similarly, the createCustomSqlitePersister function and createCustomPostgreSqlPersister function can be used to build Persister objects against SQLite and PostgreSQL SDKs (or forks) that are not already included with TinyBase.
See also
- Persistence guides
- Countries demo
- Todo App demos
- Drawing demo
Since
v1.0.0
Interfaces
Persister
A Persister object lets you save and load Store data to and from different locations, or underlying storage types.
This is useful for preserving Store or MergeableStore data between browser sessions or reloads, saving or loading browser state to or from a server, or saving Store data to disk in a environment with filesystem access.
Creating a Persister depends on the choice of underlying storage where the data is to be stored. Options include the createSessionPersister function, the createLocalPersister function, the createRemotePersister function, and the createFilePersister function, as just simple examples. The createCustomPersister function can also be used to easily create a fully customized way to save and load Store data.
Using the values of the Persists enum, the generic parameter to the Persister indicates whether it can handle a regular Store, a MergeableStore, or either. Consult the table in the overall persisters module documentation to see current support for each. The different levels of support are also described for each of the types of Persister themselves.
A Persister lets you explicit save or load data, with the save method and the load method respectively. These methods are both asynchronous (since the underlying data storage may also be) and return promises. As a result you should use the await keyword to call them in a way that guarantees subsequent execution order.
When you don't want to deal with explicit persistence operations, a Persister object also provides automatic saving and loading. Automatic saving listens for changes to the Store and persists the data immediately. Automatic loading listens (or polls) for changes to the persisted data and reflects those changes in the Store.
You can start automatic saving or loading with the startAutoSave method and startAutoLoad method. Both are asynchronous since they will do an immediate save and load before starting to listen for subsequent changes. You can stop the behavior with the stopAutoSave method and stopAutoLoad method (which are synchronous).
You may often want to have both automatic saving and loading of a Store so that changes are constantly synchronized (allowing basic state preservation between browser tabs, for example). The framework has some basic provisions to prevent race conditions - for example it will not attempt to save data if it is currently loading it and vice-versa - and will sequentially schedule methods that could cause race conditions.
That said, be aware that you should always comprehensively test your persistence strategy to understand the opportunity for data loss (in the case of trying to save data to a server under poor network conditions, for example).
To help debug such issues, since v4.0.4, the create methods for all Persister objects take an optional onIgnoredError argument. This is a handler for the errors that the Persister would otherwise ignore when trying to save or load data (such as when handling corrupted stored data). It's recommended you use this for debugging persistence issues, but only in a development environment. Database-based Persister objects also take an optional onSqlCommand argument for logging commands and queries made to the underlying database.
Examples
This example creates a Store, persists it to the browser's session storage as a JSON string, changes the persisted data, updates the Store from it, and finally destroys the Persister again.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load();
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.destroy();
sessionStorage.clear();
This example creates a Store, and automatically saves and loads it to the browser's session storage as a JSON string. Changes to the Store data, or the persisted data (implicitly firing a StorageEvent), are reflected accordingly.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"felix":{"species":"cat"}}},{}]'
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Getter methods
getStore
The getStore method returns a reference to the underlying Store or MergeableStore that is backing this Persister object.
getStore(): PersistedStore<Persist>| returns | PersistedStore<Persist> | A reference to the |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets its reference in order to update its data.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
Listener methods
addStatusListener
The addStatusListener method registers a listener function with the Persister that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<Persist>): string| Type | Description | |
|---|---|---|
listener | StatusListener<Persist> | The function that will be called whenever the |
| returns | string | A unique |
The provided listener is a StatusListener function, and will be called with a reference to the Persister and the new Status: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener method removes a listener that was previously added to the Persister.
delListener(listenerId: string): this| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | this | A reference to the |
Use the Id returned by whichever method was used to add the listener. Note that the Persister may re-use this Id for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
destroy
The destroy method should be called when this Persister object is no longer used.
destroy(): Promise<Persister<Persist>>This guarantees that all of the listeners that the object registered with the underlying Store and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad method and the stopAutoSave method in succession. This method is asynchronous.
Example
This example creates a Store, associates a Persister object with it (that registers a TablesListener with the underlying Store), and then destroys it again, removing the listener.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
await persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus method lets you find out if the Persister is currently in the process of loading or saving content.
getStatus(): StatusIt can only be doing one or the other (or neither) at any given time. The Status enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister and queries its status.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<Persister<Persist>>| Type | Description | |
|---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
| returns | Promise<Persister<Persist>> | A reference to the |
For example, a database Persister may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister.
Example
This example creates a custom Persister object against a newly-created Store and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createStore} from 'tinybase';
import {createCustomPersister} from 'tinybase/persisters';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
() => checkRemoteSystemIsReady(),
() => sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
startAutoPersisting
The startAutoPersist method is a convenience method that starts both automatic loading and saving of the Store data.
startAutoPersisting(
initialContent?: Content | () => Content,
startSaveFirst?: boolean,
): Promise<Persister<Persist>>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
startSaveFirst? | boolean | Whether to start saving before loading, defaulting to false. |
| returns | Promise<Persister<Persist>> | A Promise containing a reference to the |
This simply runs the startAutoLoad and startAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
This method can take initialContent to pass to the startAutoLoad method. See its documentation for more details.
If for some reason you want to start saving before you start loading, pass true as the second parameter.
Example
and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
persister.destroy();
Since
v6.1.0
stopAutoPersisting
The stopAutoPersist method is a convenience method that stops both automatic loading and saving of the Store data.
stopAutoPersisting(stopSaveFirst?: boolean): Promise<Persister<Persist>>| Type | Description | |
|---|---|---|
stopSaveFirst? | boolean | Whether to stop saving before loading, defaulting to false. |
| returns | Promise<Persister<Persist>> | A Promise containing a reference to the |
This simply runs the stopAutoLoad and stopAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
If for some reason you want to stop saving before you stop loading, pass true as the second parameter.
Example
automatically loading and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoPersisting();
console.log(persister.isAutoSaving());
// -> false
console.log(persister.isAutoLoading());
// -> false
persister.destroy();
Since
v6.1.0
Load methods
isAutoLoading
The isAutoLoading method lets you find out if the Persister is currently automatically loading its content.
isAutoLoading(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoLoading.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once.
load(initialContent?: Content | () => Content): Promise<Persister<Persist>>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<Persister<Persist>> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load method is called, data has previously been persisted and instead, that is loaded.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once, and then continuously.
startAutoLoad(initialContent?: Content | () => Content): Promise<Persister<Persist>>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<Persister<Persist>> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method first runs a single call to the load method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store.
This method is asynchronous because it starts by making a single call to the asynchronous load method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store, and loads data into it from the browser's session storage, which at first is empty (so the initialTables parameter is used). Subsequent changes to the underlying storage are then reflected in the Store (in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad method stops the automatic loading of data from storage previously started with the startAutoLoad method.
stopAutoLoad(): Promise<Persister<Persist>>If the Persister is not currently set to automatically load, this method has no effect. This method is asynchronous.
Example
This example creates an empty Store, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
isAutoSaving
The isAutoSaving method lets you find out if the Persister is currently automatically saving its content.
isAutoSaving(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoSaving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save method takes data from the Store with which the Persister is associated and persists it into storage, once.
save(): Promise<Persister<Persist>>This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save method takes data from the Store with which the Persister is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<Persister<Persist>>This method first runs a single call to the save method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave method stops the automatic save of data to storage previously started with the startAutoSave method.
stopAutoSave(): Promise<Persister<Persist>>If the Persister is not currently set to automatically save, this method has no effect. This method is asynchronous.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
await persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
getStats
The getStats method provides a set of statistics about the Persister, and is used for debugging purposes.
getStats(): PersisterStats| returns | PersisterStats | A |
|---|
The PersisterStats object contains a count of the number of times the Persister has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister object. Remember that the startAutoLoad method invokes an explicit load when it starts, and the startAutoSave method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store and to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Enumerations
Lifecycle enumerations
Status
The Status enum is used to indicate whether a Persister is idle, or loading or saving data.
{
Idle: 0;
Loading: 1;
Saving: 2;
}| Value | Description | |
|---|---|---|
Idle | 0 | Indicates that the |
Loading | 1 | Indicates that the |
Saving | 2 | Indicates that the |
The enum is intended to be used to understand the status of the Persister in conjunction with the getStatus and addStatusListener methods.
Note that a Persister cannot be loading and saving data at the same time.
Since
v5.3.0
Mergeable enumerations
Persists
The Persists enum is used to indicate whether a Persister can support a regular Store, a MergeableStore, or both.
{
StoreOnly: 1;
MergeableStoreOnly: 2;
StoreOrMergeableStore: 3;
}| Value | Description | |
|---|---|---|
StoreOnly | 1 | Indicates that only a regular |
MergeableStoreOnly | 2 | Indicates that only a |
StoreOrMergeableStore | 3 | Indicates that either a regular |
The enum is intended to be used by the author of a Persister to indicate which types of store can be persisted. If you discover type errors when trying to instantiate a Persister, it is most likely that you are passing in an unsupported type of store.
See the createCustomPersister method for an example of this enum being used.
Since
v5.0.0
Functions
createCustomPersister
The createCustomPersister function creates a Persister object that you can configure to persist the Store in any way you wish.
createCustomPersister<ListenerHandle, Persist>(
store: PersistedStore<Persist>,
getPersisted: () => Promise<undefined | PersistedContent<Persist>>,
setPersisted: (getContent: () => PersistedContent<Persist>, changes?: PersistedChanges<Persist, false>) => Promise<void>,
addPersisterListener: (listener: PersisterListener<Persist>) => ListenerHandle | Promise<ListenerHandle>,
delPersisterListener: (listenerHandle: ListenerHandle) => void | Promise<void>,
onIgnoredError?: (error: any) => void,
persist?: Persist,
): Persister<Persist>| Type | Description | |
|---|---|---|
store | PersistedStore<Persist> | The |
getPersisted | () => Promise<undefined | PersistedContent<Persist>> | An asynchronous function which will fetch content from the persistence layer (or |
setPersisted | (getContent: () => PersistedContent<Persist>, changes?: PersistedChanges<Persist, false>) => Promise<void> | An asynchronous function which will send content to the persistence layer. Since v4.0, it receives functions for getting the |
addPersisterListener | (listener: PersisterListener<Persist>) => ListenerHandle | Promise<ListenerHandle> | A function that will register a |
delPersisterListener | (listenerHandle: ListenerHandle) => void | Promise<void> | A function that will unregister the listener from the underlying changes to the persistence layer. It receives whatever was returned from your |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
persist? | Persist | Since v5.0, an optional integer from the |
| returns | Persister<Persist> | A reference to the new |
This is only used when developing custom Persisters, and most TinyBase users will not need to be particularly aware of it.
As well as providing a reference to the Store to persist, you must provide functions that handle how to fetch, write, and listen to, the persistence layer.
The other creation functions (such as the createSessionPersister function and createFilePersister function, for example) all use this function under the covers. See those implementations for ideas on how to implement your own Persister types.
This API changed in v4.0. Any custom persisters created on previous versions should be upgraded. Most notably, the setPersisted function parameter is provided with a getContent function to get the content from the Store itself, rather than being passed pre-serialized JSON. It also receives information about the changes made during a transaction. The getPersisted function must return the content (or nothing) rather than JSON. startListeningToPersisted has been renamed addPersisterListener, and stopListeningToPersisted has been renamed delPersisterListener.
Examples
This example creates a custom Persister object and persists a Store to a local string called persistedJson and which would automatically load by polling for changes every second. It implicitly supports only a regular Store.
import {createStore} from 'tinybase';
import {createCustomPersister} from 'tinybase/persisters';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
let persistedJson;
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return JSON.parse(persistedJson);
},
async (getContent) => {
// setPersisted
persistedJson = JSON.stringify(getContent());
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
await persister.save();
console.log(persistedJson);
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
persistedJson = '[{"pets":{"fido":{"species":"dog","color":"brown"}}},{}]';
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'brown'}}}
await persister.destroy();
This example demonstrates a Persister creation function which returns a Persister. This can persists a store to a local string called persistedJson and which would automatically load by polling for changes every second. It emits warnings to the console and explicitly supports either a Store or a MergeableStore.
import {createMergeableStore, createStore} from 'tinybase';
import {Persists, createCustomPersister} from 'tinybase/persisters';
let persistedJson;
const createJsonPersister = (storeOrMergeableStore) =>
createCustomPersister(
storeOrMergeableStore,
async () => {
// getPersisted
return JSON.parse(persistedJson);
},
async (getContent) => {
// setPersisted
persistedJson = JSON.stringify(getContent());
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
console.warn,
Persists.StoreOrMergeableStore,
);
const store = createStore();
store.setTables({pets: {fido: {species: 'dog'}}});
const storePersister = createJsonPersister(store);
await storePersister.save();
console.log(persistedJson);
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
await storePersister.destroy();
const mergeableStore = createMergeableStore('mergeableStore1');
mergeableStore.setTables({pets: {fido: {species: 'dog'}}});
const mergeableStorePersister = createJsonPersister(mergeableStore);
await mergeableStorePersister.save();
console.log(JSON.parse(persistedJson));
// ->
[
[
{
pets: [
{
fido: [
{species: ['dog', 'Nn1JUF-----Zjl0M', 4176151067]},
'',
2722999044,
],
},
'',
3367164653,
],
},
'',
30627183,
],
[{}, '', 0],
];
await mergeableStorePersister.destroy();
Since
v1.0.0
createCustomPostgreSqlPersister
The createCustomPostgreSqlPersister function creates a Persister object that you can configure to persist the Store to a PostgreSQL database.
createCustomPostgreSqlPersister<ListenerHandle, Persist>(
store: PersistedStore<Persist>,
configOrStoreTableName: undefined | string | DatabasePersisterConfig,
executeCommand: DatabaseExecuteCommand,
addChangeListener: (channel: string, listener: DatabaseChangeListener) => Promise<ListenerHandle>,
delChangeListener: (listenerHandle: ListenerHandle) => void | Promise<void>,
onSqlCommand: undefined | (sql: string, params?: any[]) => void,
onIgnoredError: undefined | (error: any) => void,
destroy: () => void,
persist: Persist,
thing: any,
getThing?: string,
): Persister<Persist>| Type | Description | |
|---|---|---|
store | PersistedStore<Persist> | The |
configOrStoreTableName | undefined | string | DatabasePersisterConfig | A |
executeCommand | DatabaseExecuteCommand | A function that will execute a command against the database. |
addChangeListener | (channel: string, listener: DatabaseChangeListener) => Promise<ListenerHandle> | An asynchronous function that will register a listener for changes to the database. |
delChangeListener | (listenerHandle: ListenerHandle) => void | Promise<void> | An asynchronous function that will unregister the listener for changes to the database. |
onSqlCommand | undefined | (sql: string, params?: any[]) => void | A function that will be called for each SQL command executed against the database. |
onIgnoredError | undefined | (error: any) => void | An optional handler for the errors that the |
destroy | () => void | A function that will be called to perform any extra clean up on the |
persist | Persist | An integer from the |
thing | any | A reference to the database or connection that can be returned with a method, by default called |
getThing? | string | An optional string that will be used to get the reference to the database or connection from the |
| returns | Persister<Persist> | A reference to the new |
This is only used when developing custom database-oriented Persisters, and most TinyBase users will not need to be particularly aware of it.
All of the TinyBase PostgreSQL-oriented Persister functions use this function under the covers, and so you may wish to look at those implementations for ideas on how to build your own Persister type, and as functional examples. Examine the implementation of the createPostgresPersister function as a good starting point, for example.
Since
v5.2.0
createCustomSqlitePersister
The createCustomSqlitePersister function creates a Persister object that you can configure to persist the Store to a SQLite database.
createCustomSqlitePersister<ListenerHandle, Persist>(
store: PersistedStore<Persist>,
configOrStoreTableName: undefined | string | DatabasePersisterConfig,
executeCommand: DatabaseExecuteCommand,
addChangeListener: (listener: DatabaseChangeListener) => ListenerHandle,
delChangeListener: (listenerHandle: ListenerHandle) => void,
onSqlCommand: undefined | (sql: string, params?: any[]) => void,
onIgnoredError: undefined | (error: any) => void,
destroy: () => void,
persist: Persist,
thing: any,
getThing?: string,
): Persister<Persist>| Type | Description | |
|---|---|---|
store | PersistedStore<Persist> | The |
configOrStoreTableName | undefined | string | DatabasePersisterConfig | A |
executeCommand | DatabaseExecuteCommand | A function that will execute a command against the database. |
addChangeListener | (listener: DatabaseChangeListener) => ListenerHandle | A function that will register a listener for changes to the database. |
delChangeListener | (listenerHandle: ListenerHandle) => void | A function that will unregister the listener for changes to the database. |
onSqlCommand | undefined | (sql: string, params?: any[]) => void | A function that will be called for each SQL command executed against the database. |
onIgnoredError | undefined | (error: any) => void | An optional handler for the errors that the |
destroy | () => void | A function that will be called to perform any extra clean up on the |
persist | Persist | An integer from the |
thing | any | A reference to the database or connection that can be returned with a method, by default called |
getThing? | string | An optional string that will be used to get the reference to the database or connection from the |
| returns | Persister<Persist> | A reference to the new SQLite-oriented |
This is only used when developing custom database-oriented Persisters, and most TinyBase users will not need to be particularly aware of it.
All of the TinyBase SQLite-oriented Persister functions use this function under the covers, and so you may wish to look at those implementations for ideas on how to build your own Persister type, and as functional examples. Examine the implementation of the createSqlite3Persister function as a good starting point, for example.
Since
v5.2.0
Type Aliases
Listener type aliases
StatusListener
The StatusListener type describes a function that is used to listen to changes to the loading and saving status of the Persister.
(
persister: Persister<Persist>,
status: Status,
): void| Type | Description | |
|---|---|---|
persister | Persister<Persist> | A reference to the |
status | Status | The new loading or saving |
| returns | void | This has no return value. |
A StatusListener is provided when using the addStatusListener method. See that method for specific examples.
When called, a StatusListener is given a reference to the Persister and the new Status: 0 means now idle, 1 means now loading, and 2 means now saving.
Since
v5.3.0
Configuration type aliases
DatabasePersisterConfig
The DatabasePersisterConfig type describes the configuration of a database-oriented Persister, such as those for SQLite and PostgreSQL.
DpcJson | DpcTabularThere are two modes for persisting a Store with a database:
- A JSON serialization of the whole
Store, which is stored in a single row of a table (normally calledtinybase) within the database. This is configured by providing aDpcJsonobject. - A tabular mapping of
TableIdsto database table names (and vice-versa).Valuesare stored in a separate special table (normally calledtinybase_values). This is configured by providing aDpcTabularobject.
Please see the DpcJson and DpcTabular type documentation for more detail on each. If not specified otherwise, JSON serialization will be used for persistence.
Changes made to the database (outside of this Persister) are picked up immediately if they are made via the same connection or library that it is using. If the database is being changed by another client, the Persister needs to poll for changes. Hence both configuration types also contain an autoLoadIntervalSeconds property which indicates how often it should do that. This defaults to 1 second.
Note that all the nested types within this type have a 'Dpc' prefix, short for 'DatabasePersisterConfig'.
Examples
When applied to a database Persister, this DatabasePersisterConfig will load and save a JSON serialization from and to a table called my_tinybase, polling the database every 2 seconds. See DpcJson for more details on these settings.
import type {DatabasePersisterConfig} from 'tinybase';
export const databasePersisterConfig: DatabasePersisterConfig = {
mode: 'json',
storeTableName: 'my_tinybase',
autoLoadIntervalSeconds: 2,
};
When applied to a database Persister, this DatabasePersisterConfig will load and save tabular data from and to tables specified in the load and save mappings. See DpcTabular for more details on these settings.
import type {DatabasePersisterConfig} from 'tinybase';
export const databasePersisterConfig: DatabasePersisterConfig = {
mode: 'tabular',
tables: {
load: {petsInDb: 'pets', speciesInDb: 'species'},
save: {pets: 'petsInDb', species: 'speciesInDb'},
},
};
Since
v4.0.0
DpcJson
The DpcJson type describes the configuration of a database-oriented Persister operating in serialized JSON mode.
{
mode: "json";
storeTableName?: string;
storeIdColumnName?: string;
storeColumnName?: string;
autoLoadIntervalSeconds?: number;
}| Type | Description | |
|---|---|---|
mode | "json" | The mode to be used for persisting the |
storeTableName? | string | An optional string which indicates the name of a table in the database which will be used to serialize the |
storeIdColumnName? | string | The optional name of the column in the database table that will be used as the |
storeColumnName? | string | The optional name of the column in the database table that will be used for the JSON of the |
autoLoadIntervalSeconds? | number | How often the |
One setting is the storeTableName property, which indicates the name of a table in the database which will be used to serialize the Store content into. It defaults to tinybase.
That table in the database will be given two columns: a primary key column called _id, and one called store. (These column names can be changed using the rowIdColumnName and storeColumnName settings). The Persister will place a single row in this table with _ in the _id column, and the JSON serialization in the store column, something like the following.
> SELECT * FROM tinybase;
+-----+-----------------------------------------------------+
| _id | store |
+-----+-----------------------------------------------------+
| _ | [{"pets":{"fido":{"species":"dog"}}},{"open":true}] |
+-----+-----------------------------------------------------+
The 'Dpc' prefix indicates that this type is used within the DatabasePersisterConfig type.
Example
When applied to a database Persister, this DatabasePersisterConfig will load and save a JSON serialization from and to a table called tinybase_json.
import type {DatabasePersisterConfig} from 'tinybase';
export const databasePersisterConfig: DatabasePersisterConfig = {
mode: 'json',
storeTableName: 'tinybase_json',
};
Since
v4.0.0
DpcTabular
The DpcTabular type describes the configuration of a database-oriented Persister that is operating in tabular mapping mode.
{
mode: "tabular";
tables?: {
load?: DpcTabularLoad;
save?: DpcTabularSave;
};
values?: DpcTabularValues;
autoLoadIntervalSeconds?: number;
}| Type | Description | |
|---|---|---|
mode | "tabular" | The mode to be used for persisting the |
tables? | { load?: DpcTabularLoad; save?: DpcTabularSave; } | The settings for how the |
values? | DpcTabularValues | The settings for how the |
autoLoadIntervalSeconds? | number | How often the |
This configuration can only be used when the Persister is persisting a regular Store. For those database-oriented Persister types that support MergeableStore data, you will need to use JSON-serialization, es described in the DpcJson section.
It is important to note that both the tabular mapping in ('save') and out ('load') of an underlying database are disabled by default. This is to ensure that if you pass in an existing populated database you don't run the immediate risk of corrupting or losing all your data.
This configuration therefore takes a tables property object (with child load and save property objects) and a values property object. These indicate how you want to load and save Tables and Values respectively. At least one of these two properties are required for the Persister to do anything!
Note that if you are planning to both load from and save to a database, it is important to make sure that the load and save table mappings are symmetrical. For example, consider the following.
import type {DatabasePersisterConfig} from 'tinybase';
export const databasePersisterConfig: DatabasePersisterConfig = {
mode: 'tabular',
tables: {
load: {petsInDb: 'pets', speciesInDb: 'species'},
save: {pets: 'petsInDb', species: 'speciesInDb'},
},
};
See the documentation for the DpcTabularLoad, DpcTabularSave, and DpcTabularValues types for more details on how to configure the tabular mapping mode.
Columns in SQLite database have no type, and so in this mode, the table can contain strings and numbers for Cells and Values, just as TinyBase does. Booleans, unfortunately, are stored as 0 or 1 in SQLite, and cannot be distinguished from numbers.
In PostgreSQL databases, all Cell and Value columns are expected to be typed as text, and the strings, booleans, and numbers are all JSON-encoded by the Persister.
The 'Dpc' prefix indicates that this type is used within the DatabasePersisterConfig type.
Example
When applied to a database Persister, this DatabasePersisterConfig will load and save Tables data from and to tables specified in the load and save mappings, and Values data from and to a table called my_tinybase_values.
import type {DatabasePersisterConfig} from 'tinybase';
export const databasePersisterConfig: DatabasePersisterConfig = {
mode: 'tabular',
tables: {
load: {petsInDb: 'pets', speciesInDb: 'species'},
save: {pets: 'petsInDb', species: 'speciesInDb'},
},
values: {
load: true,
save: true,
tableName: 'my_tinybase_values',
},
};
Since
v4.0.0
DpcTabularCondition
The DpcTabularCondition type describes the SQL WHERE clause that will be used to filter the rows that are loaded and saved to in the Store Table.
`${string}$tableName${string}` | "true"This provides a way to enact pagination or selective loading of data from the database into TinyBase so that only a fraction of the full data in the database is loaded into memory.
This clause must include at least one $tableName placeholder for the table name. For example, if you only wanted to load and save records that have an 'active' flag set, this string would be something like $tableName.active = 1.
Since
v6.1.0
DpcTabularLoad
The DpcTabularLoad type describes the configuration for loading Tables in a database-oriented Persister that is operating in tabular mode.
{[tableName: string]: {
tableId: Id;
rowIdColumnName?: string;
condition?: DpcTabularCondition;
} | Id}It is an object where each key is a name of a database table, and the value is a child configuration object for how that table should be loaded into the Store. The properties of the child configuration object are:
| Type | Description | |
|---|---|---|
tableId | Id | The Id of the Store Table into which data from this database table should be loaded. |
rowIdColumnName? | string | The optional name of the column in the database table that will be used as the Row Ids in the Store Table, defaulting to '_id'. |
condition? | string | The optional SQL WHERE clause that will be used to filter the rows that are loaded into the Store Table. When set it must include the $tableName placeholder for the table name, since v6.1.0. |
As a shortcut, if you do not need to specify a custom rowIdColumnName, you can simply provide the Id of the Store Table instead of the whole object.
The 'Dpc' prefix indicates that this type is used within the DatabasePersisterConfig type.
Example
When applied to a database Persister, this DatabasePersisterConfig will load the data of two database tables (called 'petsInDb' and 'speciesInDb') into two Store Tables (called 'pets' and 'species'). One has a column for the Row Id called 'id' and the other defaults it to '_id'.
import type {DatabasePersisterConfig} from 'tinybase';
export const databasePersisterConfig: DatabasePersisterConfig = {
mode: 'tabular',
tables: {
load: {
petsInDb: {tableId: 'pets', rowIdColumnName: 'id'},
speciesInDb: 'species',
},
},
};
Imagine database tables that look like this:
> SELECT * FROM petsInDb;
+-------+---------+-------+
| id | species | color |
+-------+---------+-------+
| fido | dog | brown |
| felix | cat | black |
+-------+---------+-------+
> SELECT * FROM speciesInDb;
+------+-------+
| _id | price |
+------+-------+
| dog | 5 |
| cat | 4 |
+------+-------+
With the configuration above, this will load into a Store with Tables that look like this:
{
"pets": {
"fido": {"species": "dog", "color": "brown"},
"felix": {"species": "cat", "color": "black"},
},
"species": {
"dog": {"price": 5},
"cat": {"price": 4},
},
}
The example above represents what happens with a SQLite Persister. In PostgreSQL databases, all Cell and Value columns are expected to be typed as text, and the strings, booleans, and numbers would be JSON-encoded if you queried them.
Since
v4.0.0
DpcTabularSave
The DpcTabularSave type describes the configuration for saving Tables in a database-oriented Persister that is operating in tabular mode.
{[tableId: Id]: {
tableName: string;
rowIdColumnName?: string;
deleteEmptyColumns?: boolean;
deleteEmptyTable?: boolean;
condition?: DpcTabularCondition;
} | string}It is an object where each key is an Id of a Store Table, and the value is a child configuration object for how that Table should be saved out to the database. The properties of the child configuration object are:
| Type | Description | |
|---|---|---|
tableName | string | The name of the database table out to which the Store Table should be saved. |
rowIdColumnName? | string | The optional name of the column in the database table that will be used to save the Row Ids from the Store Table, defaulting to '_id'. |
deleteEmptyColumns? | boolean | Whether columns in the database table will be removed if they are empty in the Store Table, defaulting to false. |
deleteEmptyTable? | boolean | Whether tables in the database will be removed if the Store Table is empty, defaulting to false. |
condition? | string | The optional SQL WHERE clause that will be used to scope cleanup operations to the Store Table. When set it must include the $tableName placeholder for the table name, since v6.1.0. Defaults to [DpcTabularLoad](#/api/persisters/type-aliases/configuration/dpctabularload/).condition. |
As a shortcut, if you do not need to specify a custom rowIdColumnName, or enable the deleteEmptyColumns or deleteEmptyTable settings, you can simply provide the name of the database table instead of the whole object.
deleteEmptyColumns and deleteEmptyTable only have a guaranteed effect when an explicit call is made to the Persister's save method. Columns and tables will not necessarily be removed when the Persister is incrementally 'autoSaving', due to performance reasons. If you want to be sure that your database table matches a TinyBase Table without any extraneous columns, simply call the save method at an idle moment.
The 'Dpc' prefix indicates that this type is used within the DatabasePersisterConfig type.
Example
When applied to a database Persister, this DatabasePersisterConfig will save the data of two Store Tables (called 'pets' and 'species') into two database tables (called 'petsInDb' and 'speciesInDb'). One has a column for the Row Id called 'id' and will delete columns and the whole table if empty, the other defaults to '_id' and will not delete columns or the whole table if empty.
import type {DatabasePersisterConfig} from 'tinybase';
export const databasePersisterConfig: DatabasePersisterConfig = {
mode: 'tabular',
tables: {
save: {
pets: {
tableName: 'petsInDb',
deleteEmptyColumns: true,
deleteEmptyTable: true,
},
species: 'speciesInDb',
},
},
};
Imagine a Store with Tables that look like this:
{
"pets": {
"fido": {"species": "dog", "color": "brown"},
"felix": {"species": "cat", "color": "black"},
},
"species": {
"dog": {"price": 5},
"cat": {"price": 4},
},
}
With the configuration above, this will save out to a database with tables that look like this:
> SELECT * FROM petsInDb;
+-------+---------+-------+
| id | species | color |
+-------+---------+-------+
| fido | dog | brown |
| felix | cat | black |
+-------+---------+-------+
> SELECT * FROM speciesInDb;
+------+-------+
| _id | price |
+------+-------+
| dog | 5 |
| cat | 4 |
+------+-------+
The example above represents what happens with a SQLite Persister. In PostgreSQL databases, all Cell and Value columns are expected to be typed as text, and the strings, booleans, and numbers would be JSON-encoded if you queried them.
Since
v4.0.0
DpcTabularValues
The DpcTabularValues type describes the configuration for handling Values in a database-oriented Persister that is operating in tabular mode.
{
load?: boolean;
save?: boolean;
tableName?: string;
}| Type | Description | |
|---|---|---|
load? | boolean | |
save? | boolean | |
tableName? | string | The optional name of the database table from and to which the |
Note that both loading and saving of Values from and to the database are disabled by default.
The 'Dpc' prefix indicates that this type is used within the DatabasePersisterConfig type.
Example
When applied to a database Persister, this DatabasePersisterConfig will load and save the data of a Store's Values into a database table called 'my_tinybase_values'.
import type {DatabasePersisterConfig} from 'tinybase';
export const databasePersisterConfig: DatabasePersisterConfig = {
mode: 'tabular',
values: {
load: true,
save: true,
tableName: 'my_tinybase_values',
},
};
Since
v4.0.0
Creation type aliases
DatabaseChangeListener
The DatabaseChangeListener type describes a function that is used to listen for changes to the data in a database.
(tableName: string): void| Type | Description | |
|---|---|---|
tableName | string | The name of the table that has changed. |
| returns | void | This has no return value. |
This is only used when developing custom database-oriented Persisters, and most TinyBase users will not need to be particularly aware of it.
This function should be called with the name of a relevant table that has changed, possible through the use of events, triggers, or notifications, dependent on the specific database implementation.
Since
v5.2.0
DatabaseExecuteCommand
The DatabaseExecuteCommand type describes a function that is used to execute commands against a database.
(
sql: string,
params?: any[],
): Promise<{[field: string]: any}[]>| Type | Description | |
|---|---|---|
sql | string | The SQL string to execute, which may include positional parameter placeholders. |
params? | any[] | An array of parameters to pass to the SQL command. |
| returns | Promise<{[field: string]: any}[]> | An promise of an array of objects, where each object represents a database result row (if the command was a query). |
This is only used when developing custom database-oriented Persisters, and most TinyBase users will not need to be particularly aware of it.
It is modelled around the common pattern of database SDKs being able to execute commands with parameters, and have those (probably asynchronous) command executions return an array of objects, where each object represents a row.
Since
v5.2.0
PersisterListener
A PersisterListener is a generic representation of the callback that lets a Persister inform the store that a change has happened to the underlying data.
(
content?: PersistedContent<Persist>,
changes?: PersistedChanges<Persist>,
): void| Type | Description | |
|---|---|---|
content? | PersistedContent<Persist> | If provided, this is a |
changes? | PersistedChanges<Persist> | If provided, this is a |
| returns | void | This has no return value. |
Using the values of the Persists enum, the generic parameter indicates whether the Persister is handling content and changes from a regular Store, a MergeableStore, or either.
If the listener is called with the changes parameter, it will be used to make an incremental change to the Store. If not, but the content parameter is available, that will be used to make a wholesale change to the Store. If neither are present, the content will be loaded using the Persister's load method. Prior to v5.0, these parameters were callbacks and the overall type was non-generic.
Since
v4.0.0
Mergeable type aliases
PersistedStore
The PersistedStore type is a generic representation of the type of store being handled by a Persister.
Persist extends Persists.StoreOrMergeableStore ? Store | MergeableStore : Persist extends Persists.MergeableStoreOnly ? MergeableStore : StoreUsing the values of the Persists enum, the generic parameter indicates whether the Persister is handling a regular Store, a MergeableStore, or either.
If the generic parameter is unspecified, the StoreOnly enum value is used, meaning that PersistedStore is equivalent to a regular Store.
Since
v5.0.0
AnyPersister
The AnyPersister type is a convenient alias for any type of Persister that can persist Store or MergeableStore objects.
Persister<Persists>Since
v5.3.0
PersistedChanges
The PersistedChanges type is a generic representation of changes made to the type of store being handled by a Persister.
Persist extends Persists.StoreOrMergeableStore ? Changes | MergeableChanges<Hashed> : Persist extends Persists.MergeableStoreOnly ? MergeableChanges<Hashed> : ChangesUsing the values of the Persists enum, the generic parameter indicates whether the Persister is handling changes for a regular Store (the Changes type), a MergeableStore (the MergeableChanges type), or either (the union of the two).
Since
v5.0.0
PersistedContent
The PersistedContent type is a generic representation of the content in the type of store being handled by a Persister.
Persist extends Persists.StoreOrMergeableStore ? Content | MergeableContent : Persist extends Persists.MergeableStoreOnly ? MergeableContent : ContentUsing the values of the Persists enum, the generic parameter indicates whether the Persister is handling content from a regular Store (the Content type), a MergeableStore (the MergeableContent type), or either (the union of the two).
If the generic parameter is unspecified, the StoreOnly enum value is used, meaning that PersistedContent is equivalent to the Content type.
Since
v5.0.0
Development type aliases
PersisterStats
The PersisterStats type describes the number of times a Persister object has loaded or saved data.
{
loads: number;
saves: number;
}| Type | Description | |
|---|---|---|
loads | number | The number of times data has been loaded. |
saves | number | The number of times data has been saved. |
A PersisterStats object is returned from the getStats method.
Since
v1.0.0
persister-automerge
The persister-automerge module of the TinyBase project provides a way to save and load Store data to and from an Automerge document.
A single entry point, the createAutomergePersister function, is provided, which returns a new Persister object that can bind a Store to a provided Automerge document handle (and in turn, its document).
See also
Since
v4.0.0
Interfaces
AutomergePersister
The AutomergePersister interface represents a Persister that lets you save and load Store data to and from an Automerge document.
You should use the createAutomergePersister function to create an AutomergePersister object.
It is a minor extension to the Persister interface and simply provides an extra getDocHandle method for accessing the Automerge document handler the Store is being persisted to.
Since
v4.3.14
Getter methods
getStore
The getStore method returns a reference to the underlying Store or MergeableStore that is backing this Persister object.
getStore(): Store| returns | Store | A reference to the |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets its reference in order to update its data.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getDocHandle
The getDocHandle method returns the Automerge document handler the Store is being persisted to.
getDocHandle(): DocHandle<any>| returns | DocHandle<any> | The Automerge document handler. |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets the Automerge document handler back out again.
import {Repo} from '@automerge/automerge-repo';
import {createStore} from 'tinybase';
import {createAutomergePersister} from 'tinybase/persisters/persister-automerge';
const docHandler = new Repo({network: []}).create();
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createAutomergePersister(store, docHandler);
console.log(persister.getDocHandle() == docHandler);
// -> true
await persister.destroy();
Since
v4.3.14
Listener methods
addStatusListener
The addStatusListener method registers a listener function with the Persister that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<StoreOnly>): string| Type | Description | |
|---|---|---|
listener | StatusListener<StoreOnly> | The function that will be called whenever the |
| returns | string | A unique |
The provided listener is a StatusListener function, and will be called with a reference to the Persister and the new Status: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener method removes a listener that was previously added to the Persister.
delListener(listenerId: string): this| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | this | A reference to the |
Use the Id returned by whichever method was used to add the listener. Note that the Persister may re-use this Id for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
destroy
The destroy method should be called when this Persister object is no longer used.
destroy(): Promise<AutomergePersister>| returns | Promise<AutomergePersister> | A Promise containing a reference to the |
|---|
This guarantees that all of the listeners that the object registered with the underlying Store and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad method and the stopAutoSave method in succession. This method is asynchronous.
Example
This example creates a Store, associates a Persister object with it (that registers a TablesListener with the underlying Store), and then destroys it again, removing the listener.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
await persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus method lets you find out if the Persister is currently in the process of loading or saving content.
getStatus(): StatusIt can only be doing one or the other (or neither) at any given time. The Status enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister and queries its status.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<AutomergePersister>| Type | Description | |
|---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
| returns | Promise<AutomergePersister> | A reference to the |
For example, a database Persister may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister.
Example
This example creates a custom Persister object against a newly-created Store and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createStore} from 'tinybase';
import {createCustomPersister} from 'tinybase/persisters';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
() => checkRemoteSystemIsReady(),
() => sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
startAutoPersisting
The startAutoPersist method is a convenience method that starts both automatic loading and saving of the Store data.
startAutoPersisting(
initialContent?: Content | () => Content,
startSaveFirst?: boolean,
): Promise<AutomergePersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
startSaveFirst? | boolean | Whether to start saving before loading, defaulting to false. |
| returns | Promise<AutomergePersister> | A Promise containing a reference to the |
This simply runs the startAutoLoad and startAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
This method can take initialContent to pass to the startAutoLoad method. See its documentation for more details.
If for some reason you want to start saving before you start loading, pass true as the second parameter.
Example
and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
persister.destroy();
Since
v6.1.0
stopAutoPersisting
The stopAutoPersist method is a convenience method that stops both automatic loading and saving of the Store data.
stopAutoPersisting(stopSaveFirst?: boolean): Promise<AutomergePersister>| Type | Description | |
|---|---|---|
stopSaveFirst? | boolean | Whether to stop saving before loading, defaulting to false. |
| returns | Promise<AutomergePersister> | A Promise containing a reference to the |
This simply runs the stopAutoLoad and stopAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
If for some reason you want to stop saving before you stop loading, pass true as the second parameter.
Example
automatically loading and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoPersisting();
console.log(persister.isAutoSaving());
// -> false
console.log(persister.isAutoLoading());
// -> false
persister.destroy();
Since
v6.1.0
Load methods
isAutoLoading
The isAutoLoading method lets you find out if the Persister is currently automatically loading its content.
isAutoLoading(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoLoading.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once.
load(initialContent?: Content | () => Content): Promise<AutomergePersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<AutomergePersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load method is called, data has previously been persisted and instead, that is loaded.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once, and then continuously.
startAutoLoad(initialContent?: Content | () => Content): Promise<AutomergePersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<AutomergePersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method first runs a single call to the load method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store.
This method is asynchronous because it starts by making a single call to the asynchronous load method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store, and loads data into it from the browser's session storage, which at first is empty (so the initialTables parameter is used). Subsequent changes to the underlying storage are then reflected in the Store (in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad method stops the automatic loading of data from storage previously started with the startAutoLoad method.
stopAutoLoad(): Promise<AutomergePersister>| returns | Promise<AutomergePersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically load, this method has no effect. This method is asynchronous.
Example
This example creates an empty Store, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
isAutoSaving
The isAutoSaving method lets you find out if the Persister is currently automatically saving its content.
isAutoSaving(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoSaving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save method takes data from the Store with which the Persister is associated and persists it into storage, once.
save(): Promise<AutomergePersister>| returns | Promise<AutomergePersister> | A Promise containing a reference to the |
|---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save method takes data from the Store with which the Persister is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<AutomergePersister>| returns | Promise<AutomergePersister> | A Promise containing a reference to the |
|---|
This method first runs a single call to the save method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave method stops the automatic save of data to storage previously started with the startAutoSave method.
stopAutoSave(): Promise<AutomergePersister>| returns | Promise<AutomergePersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically save, this method has no effect. This method is asynchronous.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
await persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
getStats
The getStats method provides a set of statistics about the Persister, and is used for debugging purposes.
getStats(): PersisterStats| returns | PersisterStats | A |
|---|
The PersisterStats object contains a count of the number of times the Persister has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister object. Remember that the startAutoLoad method invokes an explicit load when it starts, and the startAutoSave method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store and to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
createAutomergePersister
The createAutomergePersister function creates an AutomergePersister object that can persist the Store to an Automerge document.
createAutomergePersister(
store: Store,
docHandle: DocHandle<any>,
docMapName?: string,
onIgnoredError?: (error: any) => void,
): AutomergePersister| Type | Description | |
|---|---|---|
store | Store | The |
docHandle | DocHandle<any> | The Automerge document handler to persist the |
docMapName? | string | The name of the map used inside the Automerge document to sync the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | AutomergePersister | A reference to the new |
An AutomergePersister only supports regular Store objects, and cannot be used to persist the metadata of a MergeableStore.
As well as providing a reference to the Store to persist, you must provide the Automerge document handler to persist it with.
Examples
This example creates a AutomergePersister object and persists the Store to an Automerge document.
import {Repo} from '@automerge/automerge-repo';
import {createStore} from 'tinybase';
import {createAutomergePersister} from 'tinybase/persisters/persister-automerge';
const docHandler = new Repo({network: []}).create();
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createAutomergePersister(store, docHandler);
await persister.save();
// Store will be saved to the document.
console.log(await docHandler.doc());
// -> {tinybase: {t: {pets: {fido: {species: 'dog'}}}, v: {}}}
await persister.destroy();
This more complex example uses Automerge networking to keep two Store objects (each with their own Persister objects and Automerge documents) in sync with each other using a network.
import {Repo} from '@automerge/automerge-repo';
import {BroadcastChannelNetworkAdapter} from '@automerge/automerge-repo-network-broadcastchannel';
import {createStore} from 'tinybase';
import {createAutomergePersister} from 'tinybase/persisters/persister-automerge';
// Bind the first Store to a network-enabled automerge-repo
const repo1 = new Repo({
network: [new BroadcastChannelNetworkAdapter()],
});
const docHandler1 = repo1.create();
await docHandler1.doc();
const store1 = createStore();
const persister1 = createAutomergePersister(store1, docHandler1);
await persister1.startAutoLoad();
await persister1.startAutoSave();
// Bind the second Store to a different network-enabled automerge-repo
const repo2 = new Repo({
network: [new BroadcastChannelNetworkAdapter()],
});
const docHandler2 = repo2.find(docHandler1.documentId);
await docHandler2.doc();
const store2 = createStore();
const persister2 = createAutomergePersister(store2, docHandler2);
await persister2.startAutoLoad();
await persister2.startAutoSave();
// A function that waits briefly and then for the documents to synchronize
// with each other, merely for the purposes of sequentiality in this example.
const syncDocsWait = async () => {
await new Promise((resolve) => setTimeout(() => resolve(0), 100));
await docHandler1.doc();
await docHandler2.doc();
};
// Wait for the documents to synchronize in their initial state.
await syncDocsWait();
// Make a change to each of the two Stores.
store1.setTables({pets: {fido: {species: 'dog'}}});
store2.setValues({open: true});
// Wait for the documents to synchronize in their new state.
await syncDocsWait();
// Ensure the Stores are in sync.
console.log(store1.getContent());
// -> [{pets: {fido: {species: 'dog'}}}, {open: true}]
console.log(store2.getContent());
// -> [{pets: {fido: {species: 'dog'}}}, {open: true}]
await persister1.destroy();
await persister2.destroy();
Since
v4.0.0
persister-browser
The persister-browser module of the TinyBase project lets you save and load Store data to and from browser storage, including the origin private file system (OPFS).
Three entry points are provided, each of which returns a new Persister object that can load and save a Store:
- The
createSessionPersisterfunction returns aPersisterthat uses the browser's session storage. - The
createLocalPersisterfunction returns aPersisterthat uses the browser's local storage. - The
createOpfsPersisterfunction returns aPersisterthat uses a file in an origin private file system (OPFS).
See also
Persistence guides
Since
v1.0.0
Interfaces
LocalPersister
The LocalPersister interface represents a Persister that lets you save and load Store data to and from the browser's local storage.
It is a minor extension to the Persister interface and simply provides an extra getStorageName method for accessing the unique key of the storage location the Store is being persisted to.
You should use the createLocalPersister function to create a LocalPersister object.
Since
v4.3.14
Getter methods
getStore
The getStore method returns a reference to the underlying Store or MergeableStore that is backing this Persister object.
getStore(): Store | MergeableStore| returns | Store | MergeableStore | A reference to the |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets its reference in order to update its data.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getStorageName
The getStorageName method returns the unique key of the storage location the Store is being persisted to.
getStorageName(): string| returns | string | The unique key of the storage location. |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets the unique key of the storage location back out again.
import {createStore} from 'tinybase';
import {createLocalPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createLocalPersister(store, 'pets');
console.log(persister.getStorageName());
// -> 'pets'
await persister.destroy();
Since
v4.3.14
Listener methods
addStatusListener
The addStatusListener method registers a listener function with the Persister that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<StoreOrMergeableStore>): string| Type | Description | |
|---|---|---|
listener | StatusListener<StoreOrMergeableStore> | The function that will be called whenever the |
| returns | string | A unique |
The provided listener is a StatusListener function, and will be called with a reference to the Persister and the new Status: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener method removes a listener that was previously added to the Persister.
delListener(listenerId: string): this| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | this | A reference to the |
Use the Id returned by whichever method was used to add the listener. Note that the Persister may re-use this Id for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
destroy
The destroy method should be called when this Persister object is no longer used.
destroy(): Promise<LocalPersister>| returns | Promise<LocalPersister> | A Promise containing a reference to the |
|---|
This guarantees that all of the listeners that the object registered with the underlying Store and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad method and the stopAutoSave method in succession. This method is asynchronous.
Example
This example creates a Store, associates a Persister object with it (that registers a TablesListener with the underlying Store), and then destroys it again, removing the listener.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
await persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus method lets you find out if the Persister is currently in the process of loading or saving content.
getStatus(): StatusIt can only be doing one or the other (or neither) at any given time. The Status enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister and queries its status.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<LocalPersister>| Type | Description | |
|---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
| returns | Promise<LocalPersister> | A reference to the |
For example, a database Persister may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister.
Example
This example creates a custom Persister object against a newly-created Store and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createStore} from 'tinybase';
import {createCustomPersister} from 'tinybase/persisters';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
() => checkRemoteSystemIsReady(),
() => sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
startAutoPersisting
The startAutoPersist method is a convenience method that starts both automatic loading and saving of the Store data.
startAutoPersisting(
initialContent?: Content | () => Content,
startSaveFirst?: boolean,
): Promise<LocalPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
startSaveFirst? | boolean | Whether to start saving before loading, defaulting to false. |
| returns | Promise<LocalPersister> | A Promise containing a reference to the |
This simply runs the startAutoLoad and startAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
This method can take initialContent to pass to the startAutoLoad method. See its documentation for more details.
If for some reason you want to start saving before you start loading, pass true as the second parameter.
Example
and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
persister.destroy();
Since
v6.1.0
stopAutoPersisting
The stopAutoPersist method is a convenience method that stops both automatic loading and saving of the Store data.
stopAutoPersisting(stopSaveFirst?: boolean): Promise<LocalPersister>| Type | Description | |
|---|---|---|
stopSaveFirst? | boolean | Whether to stop saving before loading, defaulting to false. |
| returns | Promise<LocalPersister> | A Promise containing a reference to the |
This simply runs the stopAutoLoad and stopAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
If for some reason you want to stop saving before you stop loading, pass true as the second parameter.
Example
automatically loading and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoPersisting();
console.log(persister.isAutoSaving());
// -> false
console.log(persister.isAutoLoading());
// -> false
persister.destroy();
Since
v6.1.0
Load methods
isAutoLoading
The isAutoLoading method lets you find out if the Persister is currently automatically loading its content.
isAutoLoading(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoLoading.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once.
load(initialContent?: Content | () => Content): Promise<LocalPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<LocalPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load method is called, data has previously been persisted and instead, that is loaded.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once, and then continuously.
startAutoLoad(initialContent?: Content | () => Content): Promise<LocalPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<LocalPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method first runs a single call to the load method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store.
This method is asynchronous because it starts by making a single call to the asynchronous load method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store, and loads data into it from the browser's session storage, which at first is empty (so the initialTables parameter is used). Subsequent changes to the underlying storage are then reflected in the Store (in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad method stops the automatic loading of data from storage previously started with the startAutoLoad method.
stopAutoLoad(): Promise<LocalPersister>| returns | Promise<LocalPersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically load, this method has no effect. This method is asynchronous.
Example
This example creates an empty Store, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
isAutoSaving
The isAutoSaving method lets you find out if the Persister is currently automatically saving its content.
isAutoSaving(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoSaving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save method takes data from the Store with which the Persister is associated and persists it into storage, once.
save(): Promise<LocalPersister>| returns | Promise<LocalPersister> | A Promise containing a reference to the |
|---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save method takes data from the Store with which the Persister is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<LocalPersister>| returns | Promise<LocalPersister> | A Promise containing a reference to the |
|---|
This method first runs a single call to the save method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave method stops the automatic save of data to storage previously started with the startAutoSave method.
stopAutoSave(): Promise<LocalPersister>| returns | Promise<LocalPersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically save, this method has no effect. This method is asynchronous.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
await persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
getStats
The getStats method provides a set of statistics about the Persister, and is used for debugging purposes.
getStats(): PersisterStats| returns | PersisterStats | A |
|---|
The PersisterStats object contains a count of the number of times the Persister has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister object. Remember that the startAutoLoad method invokes an explicit load when it starts, and the startAutoSave method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store and to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
OpfsPersister
The OpfsPersister interface represents a Persister that lets you save and load Store data to and from a file in an origin private file system (OPFS).
You should use the createOpfsPersister function to create an OpfsPersister object.
It is a minor extension to the Persister interface and simply provides an extra getHandle method for accessing the file the Store is being persisted to.
Since
v6.7.0
Getter methods
getStore
The getStore method returns a reference to the underlying Store or MergeableStore that is backing this Persister object.
getStore(): Store | MergeableStore| returns | Store | MergeableStore | A reference to the |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets its reference in order to update its data.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getHandle
The getHandle method returns the handle of the file the Store is being persisted to.
getHandle(): FileSystemFileHandle| returns | FileSystemFileHandle | The handle of the file. |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets the file handle back out again.
import {createStore} from 'tinybase';
import {createOpfsPersister} from 'tinybase/persisters/persister-browser';
const opfs = await navigator.storage.getDirectory();
const handle = await opfs.getFileHandle('tinybase.json', {create: true});
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createOpfsPersister(store, handle);
console.log(persister.getHandle().name);
// -> 'tinybase.json'
await persister.destroy();
Since
v6.7.0
Listener methods
addStatusListener
The addStatusListener method registers a listener function with the Persister that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<StoreOrMergeableStore>): string| Type | Description | |
|---|---|---|
listener | StatusListener<StoreOrMergeableStore> | The function that will be called whenever the |
| returns | string | A unique |
The provided listener is a StatusListener function, and will be called with a reference to the Persister and the new Status: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener method removes a listener that was previously added to the Persister.
delListener(listenerId: string): this| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | this | A reference to the |
Use the Id returned by whichever method was used to add the listener. Note that the Persister may re-use this Id for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
destroy
The destroy method should be called when this Persister object is no longer used.
destroy(): Promise<OpfsPersister>| returns | Promise<OpfsPersister> | A Promise containing a reference to the |
|---|
This guarantees that all of the listeners that the object registered with the underlying Store and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad method and the stopAutoSave method in succession. This method is asynchronous.
Example
This example creates a Store, associates a Persister object with it (that registers a TablesListener with the underlying Store), and then destroys it again, removing the listener.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
await persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus method lets you find out if the Persister is currently in the process of loading or saving content.
getStatus(): StatusIt can only be doing one or the other (or neither) at any given time. The Status enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister and queries its status.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<OpfsPersister>| Type | Description | |
|---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
| returns | Promise<OpfsPersister> | A reference to the |
For example, a database Persister may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister.
Example
This example creates a custom Persister object against a newly-created Store and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createStore} from 'tinybase';
import {createCustomPersister} from 'tinybase/persisters';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
() => checkRemoteSystemIsReady(),
() => sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
startAutoPersisting
The startAutoPersist method is a convenience method that starts both automatic loading and saving of the Store data.
startAutoPersisting(
initialContent?: Content | () => Content,
startSaveFirst?: boolean,
): Promise<OpfsPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
startSaveFirst? | boolean | Whether to start saving before loading, defaulting to false. |
| returns | Promise<OpfsPersister> | A Promise containing a reference to the |
This simply runs the startAutoLoad and startAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
This method can take initialContent to pass to the startAutoLoad method. See its documentation for more details.
If for some reason you want to start saving before you start loading, pass true as the second parameter.
Example
and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
persister.destroy();
Since
v6.1.0
stopAutoPersisting
The stopAutoPersist method is a convenience method that stops both automatic loading and saving of the Store data.
stopAutoPersisting(stopSaveFirst?: boolean): Promise<OpfsPersister>| Type | Description | |
|---|---|---|
stopSaveFirst? | boolean | Whether to stop saving before loading, defaulting to false. |
| returns | Promise<OpfsPersister> | A Promise containing a reference to the |
This simply runs the stopAutoLoad and stopAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
If for some reason you want to stop saving before you stop loading, pass true as the second parameter.
Example
automatically loading and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoPersisting();
console.log(persister.isAutoSaving());
// -> false
console.log(persister.isAutoLoading());
// -> false
persister.destroy();
Since
v6.1.0
Load methods
isAutoLoading
The isAutoLoading method lets you find out if the Persister is currently automatically loading its content.
isAutoLoading(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoLoading.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once.
load(initialContent?: Content | () => Content): Promise<OpfsPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<OpfsPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load method is called, data has previously been persisted and instead, that is loaded.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once, and then continuously.
startAutoLoad(initialContent?: Content | () => Content): Promise<OpfsPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<OpfsPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method first runs a single call to the load method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store.
This method is asynchronous because it starts by making a single call to the asynchronous load method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store, and loads data into it from the browser's session storage, which at first is empty (so the initialTables parameter is used). Subsequent changes to the underlying storage are then reflected in the Store (in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad method stops the automatic loading of data from storage previously started with the startAutoLoad method.
stopAutoLoad(): Promise<OpfsPersister>| returns | Promise<OpfsPersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically load, this method has no effect. This method is asynchronous.
Example
This example creates an empty Store, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
isAutoSaving
The isAutoSaving method lets you find out if the Persister is currently automatically saving its content.
isAutoSaving(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoSaving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save method takes data from the Store with which the Persister is associated and persists it into storage, once.
save(): Promise<OpfsPersister>| returns | Promise<OpfsPersister> | A Promise containing a reference to the |
|---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save method takes data from the Store with which the Persister is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<OpfsPersister>| returns | Promise<OpfsPersister> | A Promise containing a reference to the |
|---|
This method first runs a single call to the save method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave method stops the automatic save of data to storage previously started with the startAutoSave method.
stopAutoSave(): Promise<OpfsPersister>| returns | Promise<OpfsPersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically save, this method has no effect. This method is asynchronous.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
await persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
getStats
The getStats method provides a set of statistics about the Persister, and is used for debugging purposes.
getStats(): PersisterStats| returns | PersisterStats | A |
|---|
The PersisterStats object contains a count of the number of times the Persister has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister object. Remember that the startAutoLoad method invokes an explicit load when it starts, and the startAutoSave method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store and to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
SessionPersister
The SessionPersister interface represents a Persister that lets you save and load Store data to and from the browser's session storage.
You should use the createSessionPersister function to create a SessionPersister object.
It is a minor extension to the Persister interface and simply provides an extra getStorageName method for accessing the unique key of the storage location the Store is being persisted to.
Since
v4.3.14
Getter methods
getStore
The getStore method returns a reference to the underlying Store or MergeableStore that is backing this Persister object.
getStore(): Store | MergeableStore| returns | Store | MergeableStore | A reference to the |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets its reference in order to update its data.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getStorageName
The getStorageName method returns the unique key of the storage location the Store is being persisted to.
getStorageName(): string| returns | string | The unique key of the storage location. |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets the unique key of the storage location back out again.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
console.log(persister.getStorageName());
// -> 'pets'
await persister.destroy();
Since
v4.3.14
Listener methods
addStatusListener
The addStatusListener method registers a listener function with the Persister that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<StoreOrMergeableStore>): string| Type | Description | |
|---|---|---|
listener | StatusListener<StoreOrMergeableStore> | The function that will be called whenever the |
| returns | string | A unique |
The provided listener is a StatusListener function, and will be called with a reference to the Persister and the new Status: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener method removes a listener that was previously added to the Persister.
delListener(listenerId: string): this| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | this | A reference to the |
Use the Id returned by whichever method was used to add the listener. Note that the Persister may re-use this Id for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
destroy
The destroy method should be called when this Persister object is no longer used.
destroy(): Promise<SessionPersister>| returns | Promise<SessionPersister> | A Promise containing a reference to the |
|---|
This guarantees that all of the listeners that the object registered with the underlying Store and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad method and the stopAutoSave method in succession. This method is asynchronous.
Example
This example creates a Store, associates a Persister object with it (that registers a TablesListener with the underlying Store), and then destroys it again, removing the listener.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
await persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus method lets you find out if the Persister is currently in the process of loading or saving content.
getStatus(): StatusIt can only be doing one or the other (or neither) at any given time. The Status enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister and queries its status.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<SessionPersister>| Type | Description | |
|---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
| returns | Promise<SessionPersister> | A reference to the |
For example, a database Persister may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister.
Example
This example creates a custom Persister object against a newly-created Store and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createStore} from 'tinybase';
import {createCustomPersister} from 'tinybase/persisters';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
() => checkRemoteSystemIsReady(),
() => sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
startAutoPersisting
The startAutoPersist method is a convenience method that starts both automatic loading and saving of the Store data.
startAutoPersisting(
initialContent?: Content | () => Content,
startSaveFirst?: boolean,
): Promise<SessionPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
startSaveFirst? | boolean | Whether to start saving before loading, defaulting to false. |
| returns | Promise<SessionPersister> | A Promise containing a reference to the |
This simply runs the startAutoLoad and startAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
This method can take initialContent to pass to the startAutoLoad method. See its documentation for more details.
If for some reason you want to start saving before you start loading, pass true as the second parameter.
Example
and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
persister.destroy();
Since
v6.1.0
stopAutoPersisting
The stopAutoPersist method is a convenience method that stops both automatic loading and saving of the Store data.
stopAutoPersisting(stopSaveFirst?: boolean): Promise<SessionPersister>| Type | Description | |
|---|---|---|
stopSaveFirst? | boolean | Whether to stop saving before loading, defaulting to false. |
| returns | Promise<SessionPersister> | A Promise containing a reference to the |
This simply runs the stopAutoLoad and stopAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
If for some reason you want to stop saving before you stop loading, pass true as the second parameter.
Example
automatically loading and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoPersisting();
console.log(persister.isAutoSaving());
// -> false
console.log(persister.isAutoLoading());
// -> false
persister.destroy();
Since
v6.1.0
Load methods
isAutoLoading
The isAutoLoading method lets you find out if the Persister is currently automatically loading its content.
isAutoLoading(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoLoading.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once.
load(initialContent?: Content | () => Content): Promise<SessionPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<SessionPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load method is called, data has previously been persisted and instead, that is loaded.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once, and then continuously.
startAutoLoad(initialContent?: Content | () => Content): Promise<SessionPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<SessionPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method first runs a single call to the load method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store.
This method is asynchronous because it starts by making a single call to the asynchronous load method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store, and loads data into it from the browser's session storage, which at first is empty (so the initialTables parameter is used). Subsequent changes to the underlying storage are then reflected in the Store (in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad method stops the automatic loading of data from storage previously started with the startAutoLoad method.
stopAutoLoad(): Promise<SessionPersister>| returns | Promise<SessionPersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically load, this method has no effect. This method is asynchronous.
Example
This example creates an empty Store, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
isAutoSaving
The isAutoSaving method lets you find out if the Persister is currently automatically saving its content.
isAutoSaving(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoSaving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save method takes data from the Store with which the Persister is associated and persists it into storage, once.
save(): Promise<SessionPersister>| returns | Promise<SessionPersister> | A Promise containing a reference to the |
|---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save method takes data from the Store with which the Persister is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<SessionPersister>| returns | Promise<SessionPersister> | A Promise containing a reference to the |
|---|
This method first runs a single call to the save method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave method stops the automatic save of data to storage previously started with the startAutoSave method.
stopAutoSave(): Promise<SessionPersister>| returns | Promise<SessionPersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically save, this method has no effect. This method is asynchronous.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
await persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
getStats
The getStats method provides a set of statistics about the Persister, and is used for debugging purposes.
getStats(): PersisterStats| returns | PersisterStats | A |
|---|
The PersisterStats object contains a count of the number of times the Persister has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister object. Remember that the startAutoLoad method invokes an explicit load when it starts, and the startAutoSave method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store and to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
createLocalPersister
The createLocalPersister function creates a LocalPersister object that can persist the Store to the browser's local storage.
createLocalPersister(
store: Store | MergeableStore,
storageName: string,
onIgnoredError?: (error: any) => void,
): LocalPersister| Type | Description | |
|---|---|---|
store | Store | MergeableStore | The |
storageName | string | The unique key to identify the storage location. |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | LocalPersister | A reference to the new |
A LocalPersister supports both regular Store and MergeableStore objects.
As well as providing a reference to the Store to persist, you must provide a storageName parameter which is unique to your application. This is the key that the browser uses to identify the storage location.
Example
This example creates a LocalPersister object and persists the Store to the browser's local storage.
import {createStore} from 'tinybase';
import {createLocalPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createLocalPersister(store, 'pets');
await persister.save();
console.log(localStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
await persister.destroy();
localStorage.clear();
Since
v1.0.0
createOpfsPersister
The createOpfsPersister function creates an OpfsPersister object that can persist the Store to a file in an origin private file system (OPFS).
createOpfsPersister(
store: Store | MergeableStore,
handle: FileSystemFileHandle,
onIgnoredError?: (error: any) => void,
): OpfsPersister| Type | Description | |
|---|---|---|
store | Store | MergeableStore | The |
handle | FileSystemFileHandle | The handle of an existing OPFS file to persist the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | OpfsPersister | A reference to the new |
An OpfsPersister supports both regular Store and MergeableStore objects.
As well as providing a reference to the Store to persist, you must provide a handle parameter which identifies an existing OPFS file to persist it to.
Example
This example creates an OpfsPersister object and persists the Store to a local file.
import {createStore} from 'tinybase';
import {createOpfsPersister} from 'tinybase/persisters/persister-browser';
const opfs = await navigator.storage.getDirectory();
const handle = await opfs.getFileHandle('tinybase.json', {create: true});
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createOpfsPersister(store, handle);
await persister.save();
// Store JSON will be saved to the file.
await persister.load();
// Store JSON will be loaded from the file.
await persister.destroy();
Since
v6.7.0
createSessionPersister
The createSessionPersister function creates a SessionPersister object that can persist the Store to the browser's session storage.
createSessionPersister(
store: Store | MergeableStore,
storageName: string,
onIgnoredError?: (error: any) => void,
): SessionPersister| Type | Description | |
|---|---|---|
store | Store | MergeableStore | The |
storageName | string | The unique key to identify the storage location. |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | SessionPersister | A reference to the new |
A SessionPersister supports both regular Store and MergeableStore objects.
As well as providing a reference to the Store to persist, you must provide a storageName parameter which is unique to your application. This is the key that the browser uses to identify the storage location.
Example
This example creates a SessionPersister object and persists the Store to the browser's session storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
persister-cr-sqlite-wasm
The persister-cr-sqlite-wasm module of the TinyBase project lets you save and load Store data to and from a local CR-SQLite database (in an appropriate environment).
See also
Database Persistence guide
Since
v4.0.0
Interfaces
CrSqliteWasmPersister
The CrSqliteWasmPersister interface represents a Persister that lets you save and load Store data to and from a local CR-SQLite database.
You should use the createCrSqliteWasmPersister function to create a CrSqliteWasmPersister object.
It is a minor extension to the Persister interface and simply provides an extra getDb method for accessing a reference to the database instance the Store is being persisted to.
Since
v4.3.14
Getter methods
getStore
The getStore method returns a reference to the underlying Store or MergeableStore that is backing this Persister object.
getStore(): Store| returns | Store | A reference to the |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets its reference in order to update its data.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getDb
The getDb method returns a reference to the database instance the Store is being persisted to.
getDb(): DB| returns | DB | A reference to the database instance. |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets the database instance back out again.
import initWasm from '@vlcn.io/crsqlite-wasm';
import {createStore} from 'tinybase';
import {createCrSqliteWasmPersister} from 'tinybase/persisters/persister-cr-sqlite-wasm';
const crSqlite3 = await initWasm();
const db = await crSqlite3.open();
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createCrSqliteWasmPersister(store, db, 'my_tinybase');
console.log(persister.getDb() == db);
// -> true
await persister.destroy();
Since
v4.3.14
Listener methods
addStatusListener
The addStatusListener method registers a listener function with the Persister that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<StoreOnly>): string| Type | Description | |
|---|---|---|
listener | StatusListener<StoreOnly> | The function that will be called whenever the |
| returns | string | A unique |
The provided listener is a StatusListener function, and will be called with a reference to the Persister and the new Status: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener method removes a listener that was previously added to the Persister.
delListener(listenerId: string): this| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | this | A reference to the |
Use the Id returned by whichever method was used to add the listener. Note that the Persister may re-use this Id for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
destroy
The destroy method should be called when this Persister object is no longer used.
destroy(): Promise<CrSqliteWasmPersister>| returns | Promise<CrSqliteWasmPersister> | A Promise containing a reference to the |
|---|
This guarantees that all of the listeners that the object registered with the underlying Store and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad method and the stopAutoSave method in succession. This method is asynchronous.
Example
This example creates a Store, associates a Persister object with it (that registers a TablesListener with the underlying Store), and then destroys it again, removing the listener.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
await persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus method lets you find out if the Persister is currently in the process of loading or saving content.
getStatus(): StatusIt can only be doing one or the other (or neither) at any given time. The Status enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister and queries its status.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<CrSqliteWasmPersister>| Type | Description | |
|---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
| returns | Promise<CrSqliteWasmPersister> | A reference to the |
For example, a database Persister may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister.
Example
This example creates a custom Persister object against a newly-created Store and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createStore} from 'tinybase';
import {createCustomPersister} from 'tinybase/persisters';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
() => checkRemoteSystemIsReady(),
() => sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
startAutoPersisting
The startAutoPersist method is a convenience method that starts both automatic loading and saving of the Store data.
startAutoPersisting(
initialContent?: Content | () => Content,
startSaveFirst?: boolean,
): Promise<CrSqliteWasmPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
startSaveFirst? | boolean | Whether to start saving before loading, defaulting to false. |
| returns | Promise<CrSqliteWasmPersister> | A Promise containing a reference to the |
This simply runs the startAutoLoad and startAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
This method can take initialContent to pass to the startAutoLoad method. See its documentation for more details.
If for some reason you want to start saving before you start loading, pass true as the second parameter.
Example
and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
persister.destroy();
Since
v6.1.0
stopAutoPersisting
The stopAutoPersist method is a convenience method that stops both automatic loading and saving of the Store data.
stopAutoPersisting(stopSaveFirst?: boolean): Promise<CrSqliteWasmPersister>| Type | Description | |
|---|---|---|
stopSaveFirst? | boolean | Whether to stop saving before loading, defaulting to false. |
| returns | Promise<CrSqliteWasmPersister> | A Promise containing a reference to the |
This simply runs the stopAutoLoad and stopAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
If for some reason you want to stop saving before you stop loading, pass true as the second parameter.
Example
automatically loading and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoPersisting();
console.log(persister.isAutoSaving());
// -> false
console.log(persister.isAutoLoading());
// -> false
persister.destroy();
Since
v6.1.0
Load methods
isAutoLoading
The isAutoLoading method lets you find out if the Persister is currently automatically loading its content.
isAutoLoading(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoLoading.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once.
load(initialContent?: Content | () => Content): Promise<CrSqliteWasmPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<CrSqliteWasmPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load method is called, data has previously been persisted and instead, that is loaded.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once, and then continuously.
startAutoLoad(initialContent?: Content | () => Content): Promise<CrSqliteWasmPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<CrSqliteWasmPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method first runs a single call to the load method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store.
This method is asynchronous because it starts by making a single call to the asynchronous load method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store, and loads data into it from the browser's session storage, which at first is empty (so the initialTables parameter is used). Subsequent changes to the underlying storage are then reflected in the Store (in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad method stops the automatic loading of data from storage previously started with the startAutoLoad method.
stopAutoLoad(): Promise<CrSqliteWasmPersister>| returns | Promise<CrSqliteWasmPersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically load, this method has no effect. This method is asynchronous.
Example
This example creates an empty Store, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
isAutoSaving
The isAutoSaving method lets you find out if the Persister is currently automatically saving its content.
isAutoSaving(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoSaving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save method takes data from the Store with which the Persister is associated and persists it into storage, once.
save(): Promise<CrSqliteWasmPersister>| returns | Promise<CrSqliteWasmPersister> | A Promise containing a reference to the |
|---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save method takes data from the Store with which the Persister is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<CrSqliteWasmPersister>| returns | Promise<CrSqliteWasmPersister> | A Promise containing a reference to the |
|---|
This method first runs a single call to the save method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave method stops the automatic save of data to storage previously started with the startAutoSave method.
stopAutoSave(): Promise<CrSqliteWasmPersister>| returns | Promise<CrSqliteWasmPersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically save, this method has no effect. This method is asynchronous.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
await persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
getStats
The getStats method provides a set of statistics about the Persister, and is used for debugging purposes.
getStats(): PersisterStats| returns | PersisterStats | A |
|---|
The PersisterStats object contains a count of the number of times the Persister has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister object. Remember that the startAutoLoad method invokes an explicit load when it starts, and the startAutoSave method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store and to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
createCrSqliteWasmPersister
The createCrSqliteWasmPersister function creates a CrSqliteWasmPersister object that can persist the Store to a local CR-SQLite database.
createCrSqliteWasmPersister(
store: Store,
db: DB,
configOrStoreTableName?: string | DatabasePersisterConfig,
onSqlCommand?: (sql: string, params?: any[]) => void,
onIgnoredError?: (error: any) => void,
): CrSqliteWasmPersister| Type | Description | |
|---|---|---|
store | Store | The |
db | DB | The database instance that was returned from |
configOrStoreTableName? | string | DatabasePersisterConfig | A |
onSqlCommand? | (sql: string, params?: any[]) => void | An optional handler called every time the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | CrSqliteWasmPersister | A reference to the new |
A CrSqliteWasmPersister only supports regular Store objects, and cannot be used to persist the metadata of a MergeableStore.
As well as providing a reference to the Store to persist, you must provide a db parameter which identifies the database instance.
A database Persister uses one of two modes: either a JSON serialization of the whole Store stored in a single row of a table (the default), or a tabular mapping of Table Ids to database table names and vice-versa).
The third argument is a DatabasePersisterConfig object that configures which of those modes to use, and settings for each. If the third argument is simply a string, it is used as the storeTableName property of the JSON serialization.
See the documentation for the DpcJson and DpcTabular types for more information on how both of those modes can be configured.
Examples
This example creates a CrSqliteWasmPersister object and persists the Store to a local CR-SQLite database as a JSON serialization into the my_tinybase table. It makes a change to the database directly and then reloads it back into the Store.
import initWasm from '@vlcn.io/crsqlite-wasm';
import {createStore} from 'tinybase';
import {createCrSqliteWasmPersister} from 'tinybase/persisters/persister-cr-sqlite-wasm';
const crSqlite3 = await initWasm();
const db = await crSqlite3.open();
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createCrSqliteWasmPersister(store, db, 'my_tinybase');
await persister.save();
// Store will be saved to the database.
console.log(await db.execO('SELECT * FROM my_tinybase;'));
// -> [{_id: '_', store: '[{"pets":{"fido":{"species":"dog"}}},{}]'}]
await db.exec(
'UPDATE my_tinybase SET store = ' +
`'[{"pets":{"felix":{"species":"cat"}}},{}]' WHERE _id = '_';`,
);
await persister.load();
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
await persister.destroy();
This example creates a CrSqliteWasmPersister object and persists the Store to a local SQLite database with tabular mapping.
import initWasm from '@vlcn.io/crsqlite-wasm';
import {createStore} from 'tinybase';
import {createCrSqliteWasmPersister} from 'tinybase/persisters/persister-cr-sqlite-wasm';
const crSqlite3 = await initWasm();
const db = await crSqlite3.open();
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createCrSqliteWasmPersister(store, db, {
mode: 'tabular',
tables: {load: {pets: 'pets'}, save: {pets: 'pets'}},
});
await persister.save();
console.log(await db.execO('SELECT * FROM pets;'));
// -> [{_id: 'fido', species: 'dog'}]
await db.exec(`INSERT INTO pets (_id, species) VALUES ('felix', 'cat')`);
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
await persister.destroy();
Since
v4.0.0
persister-durable-object-sql-storage
The persister-durable-object-sql-storage module of the TinyBase project lets you save and load Store data to and from Cloudflare Durable Object SQLite storage (in an appropriate environment).
Cloudflare's SQLite storage backend for Durable Objects offers significantly better pricing compared to the key-value storage backend. The SQLite storage backend is Cloudflare's recommended storage option for new Durable Object namespaces.
Important: Before using this persister, you must configure your Durable Object class to use SQLite storage by adding a migration to your wrangler.toml or wrangler.json configuration file. Use new_sqlite_classes in your migration configuration to enable SQLite storage for your Durable Object class.
See Cloudflare's documentation for more details.
See also
- Cloudflare Durable Objects guide
- Persistence guides
Since
v6.3.0
Interfaces
DurableObjectSqlStoragePersister
The DurableObjectSqlStoragePersister interface represents a Persister that lets you save and load Store data to and from Cloudflare Durable Object SQL storage.
You should use the createDurableObjectSqlStoragePersister function to create a DurableObjectSqlStoragePersister object, most likely within the createPersister method of a WsServerDurableObject.
It is a minor extension to the Persister interface and simply provides an extra getSqlStorage method for accessing a reference to the SQL storage that the Store is being persisted to.
Since
v6.3.0
Getter methods
getStore
The getStore method returns a reference to the underlying Store or MergeableStore that is backing this Persister object.
getStore(): MergeableStore| returns | MergeableStore | A reference to the |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets its reference in order to update its data.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getSqlStorage
The getSqlStorage method returns a reference to the SQL storage that the Store is being persisted to.
getSqlStorage(): SqlStorage| returns | SqlStorage | The reference to the SQL storage. |
|---|
Example
This example creates a Persister object against a newly-created Store (within the createPersister method of a WsServerDurableObject instance) and then gets the SQL storage reference back out again.
import {createMergeableStore} from 'tinybase';
import {createDurableObjectSqlStoragePersister} from 'tinybase/persisters/persister-durable-object-sql-storage';
import {WsServerDurableObject} from 'tinybase/synchronizers/synchronizer-ws-server-durable-object';
export class MyDurableObject extends WsServerDurableObject {
createPersister() {
const store = createMergeableStore();
const persister = createDurableObjectSqlStoragePersister(
store,
this.ctx.storage.sql,
);
console.log(persister.getSqlStorage() == this.ctx.storage.sql);
// -> true
return persister;
}
}
Since
v6.3.0
Listener methods
addStatusListener
The addStatusListener method registers a listener function with the Persister that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<MergeableStoreOnly>): string| Type | Description | |
|---|---|---|
listener | StatusListener<MergeableStoreOnly> | The function that will be called whenever the |
| returns | string | A unique |
The provided listener is a StatusListener function, and will be called with a reference to the Persister and the new Status: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener method removes a listener that was previously added to the Persister.
delListener(listenerId: string): this| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | this | A reference to the |
Use the Id returned by whichever method was used to add the listener. Note that the Persister may re-use this Id for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
destroy
The destroy method should be called when this Persister object is no longer used.
destroy(): Promise<DurableObjectSqlStoragePersister>| returns | Promise<DurableObjectSqlStoragePersister> | A Promise containing a reference to the |
|---|
This guarantees that all of the listeners that the object registered with the underlying Store and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad method and the stopAutoSave method in succession. This method is asynchronous.
Example
This example creates a Store, associates a Persister object with it (that registers a TablesListener with the underlying Store), and then destroys it again, removing the listener.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
await persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus method lets you find out if the Persister is currently in the process of loading or saving content.
getStatus(): StatusIt can only be doing one or the other (or neither) at any given time. The Status enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister and queries its status.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<DurableObjectSqlStoragePersister>| Type | Description | |
|---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
| returns | Promise<DurableObjectSqlStoragePersister> | A reference to the |
For example, a database Persister may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister.
Example
This example creates a custom Persister object against a newly-created Store and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createStore} from 'tinybase';
import {createCustomPersister} from 'tinybase/persisters';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
() => checkRemoteSystemIsReady(),
() => sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
startAutoPersisting
The startAutoPersist method is a convenience method that starts both automatic loading and saving of the Store data.
startAutoPersisting(
initialContent?: Content | () => Content,
startSaveFirst?: boolean,
): Promise<DurableObjectSqlStoragePersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
startSaveFirst? | boolean | Whether to start saving before loading, defaulting to false. |
| returns | Promise<DurableObjectSqlStoragePersister> | A Promise containing a reference to the |
This simply runs the startAutoLoad and startAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
This method can take initialContent to pass to the startAutoLoad method. See its documentation for more details.
If for some reason you want to start saving before you start loading, pass true as the second parameter.
Example
and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
persister.destroy();
Since
v6.1.0
stopAutoPersisting
The stopAutoPersist method is a convenience method that stops both automatic loading and saving of the Store data.
stopAutoPersisting(stopSaveFirst?: boolean): Promise<DurableObjectSqlStoragePersister>| Type | Description | |
|---|---|---|
stopSaveFirst? | boolean | Whether to stop saving before loading, defaulting to false. |
| returns | Promise<DurableObjectSqlStoragePersister> | A Promise containing a reference to the |
This simply runs the stopAutoLoad and stopAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
If for some reason you want to stop saving before you stop loading, pass true as the second parameter.
Example
automatically loading and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoPersisting();
console.log(persister.isAutoSaving());
// -> false
console.log(persister.isAutoLoading());
// -> false
persister.destroy();
Since
v6.1.0
Load methods
isAutoLoading
The isAutoLoading method lets you find out if the Persister is currently automatically loading its content.
isAutoLoading(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoLoading.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once.
load(initialContent?: Content | () => Content): Promise<DurableObjectSqlStoragePersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<DurableObjectSqlStoragePersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load method is called, data has previously been persisted and instead, that is loaded.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once, and then continuously.
startAutoLoad(initialContent?: Content | () => Content): Promise<DurableObjectSqlStoragePersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<DurableObjectSqlStoragePersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method first runs a single call to the load method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store.
This method is asynchronous because it starts by making a single call to the asynchronous load method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store, and loads data into it from the browser's session storage, which at first is empty (so the initialTables parameter is used). Subsequent changes to the underlying storage are then reflected in the Store (in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad method stops the automatic loading of data from storage previously started with the startAutoLoad method.
stopAutoLoad(): Promise<DurableObjectSqlStoragePersister>| returns | Promise<DurableObjectSqlStoragePersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically load, this method has no effect. This method is asynchronous.
Example
This example creates an empty Store, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
isAutoSaving
The isAutoSaving method lets you find out if the Persister is currently automatically saving its content.
isAutoSaving(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoSaving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save method takes data from the Store with which the Persister is associated and persists it into storage, once.
save(): Promise<DurableObjectSqlStoragePersister>| returns | Promise<DurableObjectSqlStoragePersister> | A Promise containing a reference to the |
|---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save method takes data from the Store with which the Persister is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<DurableObjectSqlStoragePersister>| returns | Promise<DurableObjectSqlStoragePersister> | A Promise containing a reference to the |
|---|
This method first runs a single call to the save method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave method stops the automatic save of data to storage previously started with the startAutoSave method.
stopAutoSave(): Promise<DurableObjectSqlStoragePersister>| returns | Promise<DurableObjectSqlStoragePersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically save, this method has no effect. This method is asynchronous.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
await persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
getStats
The getStats method provides a set of statistics about the Persister, and is used for debugging purposes.
getStats(): PersisterStats| returns | PersisterStats | A |
|---|
The PersisterStats object contains a count of the number of times the Persister has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister object. Remember that the startAutoLoad method invokes an explicit load when it starts, and the startAutoSave method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store and to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
createDurableObjectSqlStoragePersister
The createDurableObjectSqlStoragePersister function creates a DurableObjectSqlStoragePersister object that can persist the Store to and from Cloudflare Durable Object SQLite storage.
createDurableObjectSqlStoragePersister(
store: MergeableStore,
sqlStorage: SqlStorage,
configOrStoreTableName?: string | DurableObjectSqlDatabasePersisterConfig,
onSqlCommand?: (sql: string, params?: any[]) => void,
onIgnoredError?: (error: any) => void,
): DurableObjectSqlStoragePersister| Type | Description | |
|---|---|---|
store | MergeableStore | The |
sqlStorage | SqlStorage | The Durable Object SQL storage to persist the |
configOrStoreTableName? | string | DurableObjectSqlDatabasePersisterConfig | A |
onSqlCommand? | (sql: string, params?: any[]) => void | An optional handler called every time the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | DurableObjectSqlStoragePersister | A reference to the new |
You will mostly use this within the createPersister method of a WsServerDurableObject.
This persister uses Cloudflare's SQLite storage backend, which provides better pricing and performance compared to the legacy Key-value storage backend.
Important Prerequisites: Before using this persister, you must configure your Durable Object class to use SQLite storage by adding a migration to your Wrangler configuration file. In your wrangler.toml, add the following.
[[migrations]]
tag = "v1"
new_sqlite_classes = ["YourDurableObjectClass"]
Or in your wrangler.json, add:
{
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["YourDurableObjectClass"]
}
]
}
For more details on Durable Object migrations, see the Cloudflare documentation.
A database Persister uses one of two modes: either a JSON serialization of the whole Store stored in a single row of a table (the default), a fragmented mode that stores each piece of data separately to avoid Cloudflare's 2MB row limit.
JSON Mode (Default): Stores the entire
Storeas JSON in a single database row. This is efficient for smaller stores but may hit Cloudflare's 2MB row limit for very large stores and uses fewer database writes.Fragmented Mode: Stores each table, row, cell, and value as separate database rows. Use this mode if you're concerned about hitting Cloudflare's 2MB row limit with large stores in JSON mode. This mode creates more database writes but avoids row size limitations.
The third argument is a DatabasePersisterConfig object that configures which of those modes to use, and settings for each. If the third argument is simply a string, it is used as the storeTableName property of the JSON serialization. If it is the string 'fragmented', it enables fragmented mode.
See the documentation for the DpcJson, DpcFragmented, and DpcTabular types for more information on how all of those modes can be configured.
As well as providing a reference to the Store or MergeableStore to persist, you must provide a sqlStorage parameter which identifies the Durable Object SQLite storage to persist it to.
Examples
This example creates a DurableObjectSqlStoragePersister object and persists the Store to Durable Object SQLite storage as a JSON serialization into the default tinybase table. It uses this within the createPersister method of a WsServerDurableObject instance.
import {createMergeableStore} from 'tinybase';
import {createDurableObjectSqlStoragePersister} from 'tinybase/persisters/persister-durable-object-sql-storage';
import {WsServerDurableObject} from 'tinybase/synchronizers/synchronizer-ws-server-durable-object';
export class MyDurableObject extends WsServerDurableObject {
createPersister() {
const store = createMergeableStore();
const persister = createDurableObjectSqlStoragePersister(
store,
this.ctx.storage.sql,
);
return persister;
}
}
This example creates a DurableObjectSqlStoragePersister object with a custom table name and SQL command logging for debugging.
import {createMergeableStore} from 'tinybase';
import {createDurableObjectSqlStoragePersister} from 'tinybase/persisters/persister-durable-object-sql-storage';
import {WsServerDurableObject} from 'tinybase/synchronizers/synchronizer-ws-server-durable-object';
export class MyDurableObject extends WsServerDurableObject {
createPersister() {
const store = createMergeableStore();
const persister = createDurableObjectSqlStoragePersister(
store,
this.ctx.storage.sql,
'my_app_store',
(sql, params) => console.log('SQL:', sql, params),
(error) => console.error('Persistence error:', error),
);
return persister;
}
}
This example creates a DurableObjectSqlStoragePersister object using fragmented mode to avoid Cloudflare's 2MB row limit for large stores.
import {createMergeableStore} from 'tinybase';
import {createDurableObjectSqlStoragePersister} from 'tinybase/persisters/persister-durable-object-sql-storage';
import {WsServerDurableObject} from 'tinybase/synchronizers/synchronizer-ws-server-durable-object';
export class MyDurableObject extends WsServerDurableObject {
createPersister() {
const store = createMergeableStore();
const persister = createDurableObjectSqlStoragePersister(
store,
this.ctx.storage.sql,
{mode: 'fragmented'},
);
return persister;
}
}
This example creates a DurableObjectSqlStoragePersister object using fragmented mode with a custom storage prefix.
import {createMergeableStore} from 'tinybase';
import {createDurableObjectSqlStoragePersister} from 'tinybase/persisters/persister-durable-object-sql-storage';
import {WsServerDurableObject} from 'tinybase/synchronizers/synchronizer-ws-server-durable-object';
export class MyDurableObject extends WsServerDurableObject {
createPersister() {
const store = createMergeableStore();
const persister = createDurableObjectSqlStoragePersister(
store,
this.ctx.storage.sql,
{mode: 'fragmented', storagePrefix: 'my_app_'},
);
return persister;
}
}
Since
v6.3.0
Type Aliases
DpcFragmented
The DpcFragmented type represents the configuration for fragmented persistence mode in a DurableObjectSqlStoragePersister.
{
mode: "fragmented";
storagePrefix?: string;
}| Type | Description | |
|---|---|---|
mode | "fragmented" | The mode property must be set to 'fragmented' to enable fragmented persistence mode. |
storagePrefix? | string | The storagePrefix property lets you specify an optional prefix for the database table names used in fragmented mode. |
This mode stores each table, row, cell, and value as separate database rows, avoiding Cloudflare's 2MB row limit that can be hit with large stores in JSON mode. While this creates more database writes, it provides better scalability for larger datasets.
Example
This example shows how to configure a DurableObjectSqlStoragePersister to use fragmented mode with a custom storage prefix.
import {createMergeableStore} from 'tinybase';
import {createDurableObjectSqlStoragePersister} from 'tinybase/persisters/persister-durable-object-sql-storage';
import {WsServerDurableObject} from 'tinybase/synchronizers/synchronizer-ws-server-durable-object';
const config = {
mode: 'fragmented',
storagePrefix: 'my_app_',
};
export class MyDurableObject extends WsServerDurableObject {
createPersister() {
const store = createMergeableStore();
const persister = createDurableObjectSqlStoragePersister(
store,
this.ctx.storage.sql,
config,
);
return persister;
}
}
Since
v6.3.0
DurableObjectSqlDatabasePersisterConfig
The DurableObjectSqlDatabasePersisterConfig type represents the union of all possible configuration types for a DurableObjectSqlStoragePersister.
DpcJson | DpcFragmentedThis allows the persister to support multiple persistence modes.
- JSON mode (via
DpcJson): Stores the entireStoreas JSON in a single row. - Fragmented mode (via
DpcFragmented): Stores each piece of data as separate rows.
Example
These examples show some different configuration options.
// JSON mode (default)
{
mode: 'json',
storeTableName: 'my_store',
};
// Fragmented mode
{
mode: 'fragmented',
storagePrefix: 'app_',
};
Since
v6.3.0
persister-durable-object-storage
The persister-durable-object-storage module of the TinyBase project lets you save and load Store data to and from Cloudflare Durable Object storage (in an appropriate environment).
See also
- Cloudflare Durable Objects guide
- Persistence guides
Since
v5.4.0
Interfaces
DurableObjectStoragePersister
The DurableObjectStoragePersister interface represents a Persister that lets you save and load Store data to and from Cloudflare Durable Object storage.
You should use the createDurableObjectStoragePersister function to create a DurableObjectStoragePersister object, most likely within the createPersister method of a WsServerDurableObject.
It is a minor extension to the Persister interface and simply provides an extra getStorage method for accessing a reference to the storage that the Store is being persisted to.
Since
v5.4.0
Getter methods
getStore
The getStore method returns a reference to the underlying Store or MergeableStore that is backing this Persister object.
getStore(): MergeableStore| returns | MergeableStore | A reference to the |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets its reference in order to update its data.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getStorage
The getStorage method returns a reference to the storage that the Store is being persisted to.
getStorage(): DurableObjectStorage| returns | DurableObjectStorage | The reference to the storage. |
|---|
Example
This example creates a Persister object against a newly-created Store (within the createPersister method of a WsServerDurableObject instance) and then gets the storage reference back out again.
import {createMergeableStore} from 'tinybase';
import {createDurableObjectStoragePersister} from 'tinybase/persisters/persister-durable-object-storage';
import {WsServerDurableObject} from 'tinybase/synchronizers/synchronizer-ws-server-durable-object';
export class MyDurableObject extends WsServerDurableObject {
createPersister() {
const store = createMergeableStore();
const persister = createDurableObjectStoragePersister(
store,
this.ctx.storage,
);
console.log(persister.getStorage() == this.ctx.storage);
// -> true
return persister;
}
}
Since
v5.4.0
Listener methods
addStatusListener
The addStatusListener method registers a listener function with the Persister that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<MergeableStoreOnly>): string| Type | Description | |
|---|---|---|
listener | StatusListener<MergeableStoreOnly> | The function that will be called whenever the |
| returns | string | A unique |
The provided listener is a StatusListener function, and will be called with a reference to the Persister and the new Status: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener method removes a listener that was previously added to the Persister.
delListener(listenerId: string): this| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | this | A reference to the |
Use the Id returned by whichever method was used to add the listener. Note that the Persister may re-use this Id for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
destroy
The destroy method should be called when this Persister object is no longer used.
destroy(): Promise<DurableObjectStoragePersister>| returns | Promise<DurableObjectStoragePersister> | A Promise containing a reference to the |
|---|
This guarantees that all of the listeners that the object registered with the underlying Store and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad method and the stopAutoSave method in succession. This method is asynchronous.
Example
This example creates a Store, associates a Persister object with it (that registers a TablesListener with the underlying Store), and then destroys it again, removing the listener.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
await persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus method lets you find out if the Persister is currently in the process of loading or saving content.
getStatus(): StatusIt can only be doing one or the other (or neither) at any given time. The Status enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister and queries its status.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<DurableObjectStoragePersister>| Type | Description | |
|---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
| returns | Promise<DurableObjectStoragePersister> | A reference to the |
For example, a database Persister may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister.
Example
This example creates a custom Persister object against a newly-created Store and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createStore} from 'tinybase';
import {createCustomPersister} from 'tinybase/persisters';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
() => checkRemoteSystemIsReady(),
() => sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
startAutoPersisting
The startAutoPersist method is a convenience method that starts both automatic loading and saving of the Store data.
startAutoPersisting(
initialContent?: Content | () => Content,
startSaveFirst?: boolean,
): Promise<DurableObjectStoragePersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
startSaveFirst? | boolean | Whether to start saving before loading, defaulting to false. |
| returns | Promise<DurableObjectStoragePersister> | A Promise containing a reference to the |
This simply runs the startAutoLoad and startAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
This method can take initialContent to pass to the startAutoLoad method. See its documentation for more details.
If for some reason you want to start saving before you start loading, pass true as the second parameter.
Example
and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
persister.destroy();
Since
v6.1.0
stopAutoPersisting
The stopAutoPersist method is a convenience method that stops both automatic loading and saving of the Store data.
stopAutoPersisting(stopSaveFirst?: boolean): Promise<DurableObjectStoragePersister>| Type | Description | |
|---|---|---|
stopSaveFirst? | boolean | Whether to stop saving before loading, defaulting to false. |
| returns | Promise<DurableObjectStoragePersister> | A Promise containing a reference to the |
This simply runs the stopAutoLoad and stopAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
If for some reason you want to stop saving before you stop loading, pass true as the second parameter.
Example
automatically loading and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoPersisting();
console.log(persister.isAutoSaving());
// -> false
console.log(persister.isAutoLoading());
// -> false
persister.destroy();
Since
v6.1.0
Load methods
isAutoLoading
The isAutoLoading method lets you find out if the Persister is currently automatically loading its content.
isAutoLoading(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoLoading.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once.
load(initialContent?: Content | () => Content): Promise<DurableObjectStoragePersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<DurableObjectStoragePersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load method is called, data has previously been persisted and instead, that is loaded.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once, and then continuously.
startAutoLoad(initialContent?: Content | () => Content): Promise<DurableObjectStoragePersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<DurableObjectStoragePersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method first runs a single call to the load method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store.
This method is asynchronous because it starts by making a single call to the asynchronous load method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store, and loads data into it from the browser's session storage, which at first is empty (so the initialTables parameter is used). Subsequent changes to the underlying storage are then reflected in the Store (in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad method stops the automatic loading of data from storage previously started with the startAutoLoad method.
stopAutoLoad(): Promise<DurableObjectStoragePersister>| returns | Promise<DurableObjectStoragePersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically load, this method has no effect. This method is asynchronous.
Example
This example creates an empty Store, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
isAutoSaving
The isAutoSaving method lets you find out if the Persister is currently automatically saving its content.
isAutoSaving(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoSaving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save method takes data from the Store with which the Persister is associated and persists it into storage, once.
save(): Promise<DurableObjectStoragePersister>| returns | Promise<DurableObjectStoragePersister> | A Promise containing a reference to the |
|---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save method takes data from the Store with which the Persister is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<DurableObjectStoragePersister>| returns | Promise<DurableObjectStoragePersister> | A Promise containing a reference to the |
|---|
This method first runs a single call to the save method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave method stops the automatic save of data to storage previously started with the startAutoSave method.
stopAutoSave(): Promise<DurableObjectStoragePersister>| returns | Promise<DurableObjectStoragePersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically save, this method has no effect. This method is asynchronous.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
await persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
getStats
The getStats method provides a set of statistics about the Persister, and is used for debugging purposes.
getStats(): PersisterStats| returns | PersisterStats | A |
|---|
The PersisterStats object contains a count of the number of times the Persister has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister object. Remember that the startAutoLoad method invokes an explicit load when it starts, and the startAutoSave method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store and to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
createDurableObjectStoragePersister
The createDurableObjectStoragePersister function creates a DurableObjectStoragePersister object that can persist the Store to and from Cloudflare Durable Object storage.
createDurableObjectStoragePersister(
store: MergeableStore,
storage: DurableObjectStorage,
storagePrefix?: string,
onIgnoredError?: (error: any) => void,
): DurableObjectStoragePersister| Type | Description | |
|---|---|---|
store | MergeableStore | The |
storage | DurableObjectStorage | The Durable Object storage to persist the |
storagePrefix? | string | An optional prefix to use on the keys in storage, which is useful if you want to ensure the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | DurableObjectStoragePersister | A reference to the new |
You will mostly use this within the createPersister method of a WsServerDurableObject.
A DurableObjectStoragePersister only supports MergeableStore objects, and cannot be used to persist a regular Store.
Durable Objects have limitations on the data that can be stored in each key of their key-value structure. The DurableObjectStoragePersister uses one key per TinyBase Value, one key per Cell, one key per Row, and one key per Table. Mostly this is CRDT metadata, but the main caution is to ensure that each individual TinyBase Cell and Value data does not exceed the (128 KiB) limit.
As well as providing a reference to the MergeableStore to persist, you must provide a storage parameter which identifies the Durable Object storage to persist it to.
Example
This example creates a Persister object against a newly-created MergeableStore (within the createPersister method of a WsServerDurableObject instance) and then gets the storage reference back out again.
import {createMergeableStore} from 'tinybase';
import {createDurableObjectStoragePersister} from 'tinybase/persisters/persister-durable-object-storage';
import {WsServerDurableObject} from 'tinybase/synchronizers/synchronizer-ws-server-durable-object';
export class MyDurableObject extends WsServerDurableObject {
createPersister() {
const store = createMergeableStore();
const persister = createDurableObjectStoragePersister(
store,
this.ctx.storage,
);
return persister;
}
}
Since
v5.4.0
persister-electric-sql
The persister-electric-sql module of the TinyBase project lets you save and load Store data to and from a local ElectricSQL database (in an appropriate environment).
See also
Database Persistence guide
Since
v4.6.0
Interfaces
ElectricSqlPersister
The ElectricSqlPersister interface represents a Persister that lets you save and load Store data to and from a local ElectricSQL database.
You should use the createElectricSqlPersister function to create an ElectricSqlPersister object.
It is a minor extension to the Persister interface and simply provides an extra getElectricClient method for accessing a reference to the Electric client the Store is being persisted to.
Since
v4.6.0
Getter methods
getStore
The getStore method returns a reference to the underlying Store or MergeableStore that is backing this Persister object.
getStore(): Store| returns | Store | A reference to the |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets its reference in order to update its data.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getElectricClient
The getElectricClient method returns a reference to the Electric client the Store is being persisted to.
getElectricClient(): ElectricClient<any>| returns | ElectricClient<any> | A reference to the Electric client. |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets the Electric client back out again.
import {ElectricDatabase, electrify} from 'electric-sql/wa-sqlite';
import {createStore} from 'tinybase';
import {createElectricSqlPersister} from 'tinybase/persisters/persister-electric-sql';
import {schema} from './generated/client';
const electricClient = await electrify(
await ElectricDatabase.init('electric.db', ''),
schema,
{url: import.meta.env.ELECTRIC_SERVICE},
);
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createElectricSqlPersister(
store,
electricClient,
'my_tinybase',
);
console.log(persister.getElectricClient() == electricClient);
// -> true
await persister.destroy();
Since
v4.6.0
Listener methods
addStatusListener
The addStatusListener method registers a listener function with the Persister that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<StoreOnly>): string| Type | Description | |
|---|---|---|
listener | StatusListener<StoreOnly> | The function that will be called whenever the |
| returns | string | A unique |
The provided listener is a StatusListener function, and will be called with a reference to the Persister and the new Status: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener method removes a listener that was previously added to the Persister.
delListener(listenerId: string): this| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | this | A reference to the |
Use the Id returned by whichever method was used to add the listener. Note that the Persister may re-use this Id for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
destroy
The destroy method should be called when this Persister object is no longer used.
destroy(): Promise<ElectricSqlPersister>| returns | Promise<ElectricSqlPersister> | A Promise containing a reference to the |
|---|
This guarantees that all of the listeners that the object registered with the underlying Store and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad method and the stopAutoSave method in succession. This method is asynchronous.
Example
This example creates a Store, associates a Persister object with it (that registers a TablesListener with the underlying Store), and then destroys it again, removing the listener.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
await persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus method lets you find out if the Persister is currently in the process of loading or saving content.
getStatus(): StatusIt can only be doing one or the other (or neither) at any given time. The Status enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister and queries its status.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<ElectricSqlPersister>| Type | Description | |
|---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
| returns | Promise<ElectricSqlPersister> | A reference to the |
For example, a database Persister may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister.
Example
This example creates a custom Persister object against a newly-created Store and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createStore} from 'tinybase';
import {createCustomPersister} from 'tinybase/persisters';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
() => checkRemoteSystemIsReady(),
() => sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
startAutoPersisting
The startAutoPersist method is a convenience method that starts both automatic loading and saving of the Store data.
startAutoPersisting(
initialContent?: Content | () => Content,
startSaveFirst?: boolean,
): Promise<ElectricSqlPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
startSaveFirst? | boolean | Whether to start saving before loading, defaulting to false. |
| returns | Promise<ElectricSqlPersister> | A Promise containing a reference to the |
This simply runs the startAutoLoad and startAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
This method can take initialContent to pass to the startAutoLoad method. See its documentation for more details.
If for some reason you want to start saving before you start loading, pass true as the second parameter.
Example
and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
persister.destroy();
Since
v6.1.0
stopAutoPersisting
The stopAutoPersist method is a convenience method that stops both automatic loading and saving of the Store data.
stopAutoPersisting(stopSaveFirst?: boolean): Promise<ElectricSqlPersister>| Type | Description | |
|---|---|---|
stopSaveFirst? | boolean | Whether to stop saving before loading, defaulting to false. |
| returns | Promise<ElectricSqlPersister> | A Promise containing a reference to the |
This simply runs the stopAutoLoad and stopAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
If for some reason you want to stop saving before you stop loading, pass true as the second parameter.
Example
automatically loading and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoPersisting();
console.log(persister.isAutoSaving());
// -> false
console.log(persister.isAutoLoading());
// -> false
persister.destroy();
Since
v6.1.0
Load methods
isAutoLoading
The isAutoLoading method lets you find out if the Persister is currently automatically loading its content.
isAutoLoading(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoLoading.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once.
load(initialContent?: Content | () => Content): Promise<ElectricSqlPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<ElectricSqlPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load method is called, data has previously been persisted and instead, that is loaded.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once, and then continuously.
startAutoLoad(initialContent?: Content | () => Content): Promise<ElectricSqlPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<ElectricSqlPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method first runs a single call to the load method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store.
This method is asynchronous because it starts by making a single call to the asynchronous load method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store, and loads data into it from the browser's session storage, which at first is empty (so the initialTables parameter is used). Subsequent changes to the underlying storage are then reflected in the Store (in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad method stops the automatic loading of data from storage previously started with the startAutoLoad method.
stopAutoLoad(): Promise<ElectricSqlPersister>| returns | Promise<ElectricSqlPersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically load, this method has no effect. This method is asynchronous.
Example
This example creates an empty Store, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
isAutoSaving
The isAutoSaving method lets you find out if the Persister is currently automatically saving its content.
isAutoSaving(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoSaving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save method takes data from the Store with which the Persister is associated and persists it into storage, once.
save(): Promise<ElectricSqlPersister>| returns | Promise<ElectricSqlPersister> | A Promise containing a reference to the |
|---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save method takes data from the Store with which the Persister is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<ElectricSqlPersister>| returns | Promise<ElectricSqlPersister> | A Promise containing a reference to the |
|---|
This method first runs a single call to the save method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave method stops the automatic save of data to storage previously started with the startAutoSave method.
stopAutoSave(): Promise<ElectricSqlPersister>| returns | Promise<ElectricSqlPersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically save, this method has no effect. This method is asynchronous.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
await persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
getStats
The getStats method provides a set of statistics about the Persister, and is used for debugging purposes.
getStats(): PersisterStats| returns | PersisterStats | A |
|---|
The PersisterStats object contains a count of the number of times the Persister has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister object. Remember that the startAutoLoad method invokes an explicit load when it starts, and the startAutoSave method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store and to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
createElectricSqlPersister
The createElectricSqlPersister function creates an ElectricSqlPersister object that can persist a Store to a local ElectricSQL database.
createElectricSqlPersister(
store: Store,
electricClient: ElectricClient<any>,
configOrStoreTableName?: string | DatabasePersisterConfig,
onSqlCommand?: (sql: string, params?: any[]) => void,
onIgnoredError?: (error: any) => void,
): ElectricSqlPersister| Type | Description | |
|---|---|---|
store | Store | The |
electricClient | ElectricClient<any> | The Electric client that was returned from |
configOrStoreTableName? | string | DatabasePersisterConfig | A |
onSqlCommand? | (sql: string, params?: any[]) => void | An optional handler called every time the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | ElectricSqlPersister | A reference to the new |
An ElectricSqlPersister only supports regular Store objects, and cannot be used to persist the metadata of a MergeableStore.
As well as providing a reference to the Store to persist, you must provide a electricClient parameter which identifies the Electric client.
A database Persister uses one of two modes: either a JSON serialization of the whole Store stored in a single row of a table (the default), or a tabular mapping of Table Ids to database table names and vice-versa).
The third argument is a DatabasePersisterConfig object that configures which of those modes to use, and settings for each. If the third argument is simply a string, it is used as the storeTableName property of the JSON serialization.
See the documentation for the DpcJson and DpcTabular types for more information on how both of those modes can be configured.
Examples
This example creates a ElectricSqlPersister object and persists the Store to a local ElectricSql database as a JSON serialization into the my_tinybase table. It makes a change to the database directly and then reloads it back into the Store.
import {ElectricDatabase, electrify} from 'electric-sql/wa-sqlite';
import {createStore} from 'tinybase';
import {createElectricSqlPersister} from 'tinybase/persisters/persister-electric-sql';
import {schema} from './generated/client';
const electricClient = await electrify(
await ElectricDatabase.init('electric.db', ''),
schema,
{url: import.meta.env.ELECTRIC_SERVICE},
);
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createElectricSqlPersister(
store,
electricClient,
'my_tinybase',
);
await persister.save();
// Store will be saved to the database.
console.log(
await electricClient.db.raw({sql: 'SELECT * FROM my_tinybase;'}),
);
// -> [{_id: '_', store: '[{"pets":{"fido":{"species":"dog"}}},{}]'}]
await electricClient.db.raw({
sql:
'UPDATE my_tinybase SET store = ' +
`'[{"pets":{"felix":{"species":"cat"}}},{}]' WHERE _id = '_';`,
});
await persister.load();
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
await persister.destroy();
This example creates a ElectricSqlPersister object and persists the Store to a local ElectricSql database with tabular mapping.
import {ElectricDatabase, electrify} from 'electric-sql/wa-sqlite';
import {createStore} from 'tinybase';
import {createElectricSqlPersister} from 'tinybase/persisters/persister-electric-sql';
import {schema} from './generated/client';
const electricClient = await electrify(
await ElectricDatabase.init('electric.db', ''),
schema,
{url: import.meta.env.ELECTRIC_SERVICE},
);
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createElectricSqlPersister(store, electricClient, {
mode: 'tabular',
tables: {load: {pets: 'pets'}, save: {pets: 'pets'}},
});
await persister.save();
console.log(await electricClient.db.raw({sql: 'SELECT * FROM pets;'}));
// -> [{_id: 'fido', species: 'dog'}]
await electricClient.db.raw({
sql: `INSERT INTO pets (_id, species) VALUES ('felix', 'cat')`,
});
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
await persister.destroy();
Since
v4.6.0
persister-expo-sqlite
The persister-expo-sqlite module of the TinyBase project lets you save and load Store data to and from a Expo-SQLite database (in an appropriate React Native environment).
As of TinyBase v5.0, this module provides a Persister for the modern version of Expo's SQLite library, designated 'next' as of November 2023 and default as v14.0 by June 2024.
Note that TinyBase support for the legacy version of Expo-SQLite is no longer available.
See also
Database Persistence guide
Since
v4.5.0
Interfaces
ExpoSqlitePersister
The ExpoSqlitePersister interface represents a Persister that lets you save and load Store data to and from a Expo-SQLite database.
You should use the createExpoSqlitePersister function to create an ExpoSqlitePersister object.
It is a minor extension to the Persister interface and simply provides an extra getDb method for accessing a reference to the database instance the Store is being persisted to.
Since
v4.5.0
Getter methods
getStore
The getStore method returns a reference to the underlying Store or MergeableStore that is backing this Persister object.
getStore(): Store | MergeableStore| returns | Store | MergeableStore | A reference to the |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets its reference in order to update its data.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getDb
The getDb method returns a reference to the database instance the Store is being persisted to.
getDb(): SQLiteDatabase| returns | SQLiteDatabase | A reference to the database instance. |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets the database instance back out again.
import {openDatabaseSync} from 'expo-sqlite';
import {createStore} from 'tinybase';
import {createExpoSqlitePersister} from 'tinybase/persisters/persister-expo-sqlite';
const db = openDatabaseSync('my.db');
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createExpoSqlitePersister(store, db, 'my_tinybase');
console.log(persister.getDb() == db);
// -> true
await persister.destroy();
Since
v4.5.0
Listener methods
addStatusListener
The addStatusListener method registers a listener function with the Persister that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<StoreOrMergeableStore>): string| Type | Description | |
|---|---|---|
listener | StatusListener<StoreOrMergeableStore> | The function that will be called whenever the |
| returns | string | A unique |
The provided listener is a StatusListener function, and will be called with a reference to the Persister and the new Status: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener method removes a listener that was previously added to the Persister.
delListener(listenerId: string): this| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | this | A reference to the |
Use the Id returned by whichever method was used to add the listener. Note that the Persister may re-use this Id for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
destroy
The destroy method should be called when this Persister object is no longer used.
destroy(): Promise<ExpoSqlitePersister>| returns | Promise<ExpoSqlitePersister> | A Promise containing a reference to the |
|---|
This guarantees that all of the listeners that the object registered with the underlying Store and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad method and the stopAutoSave method in succession. This method is asynchronous.
Example
This example creates a Store, associates a Persister object with it (that registers a TablesListener with the underlying Store), and then destroys it again, removing the listener.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
await persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus method lets you find out if the Persister is currently in the process of loading or saving content.
getStatus(): StatusIt can only be doing one or the other (or neither) at any given time. The Status enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister and queries its status.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<ExpoSqlitePersister>| Type | Description | |
|---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
| returns | Promise<ExpoSqlitePersister> | A reference to the |
For example, a database Persister may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister.
Example
This example creates a custom Persister object against a newly-created Store and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createStore} from 'tinybase';
import {createCustomPersister} from 'tinybase/persisters';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
() => checkRemoteSystemIsReady(),
() => sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
startAutoPersisting
The startAutoPersist method is a convenience method that starts both automatic loading and saving of the Store data.
startAutoPersisting(
initialContent?: Content | () => Content,
startSaveFirst?: boolean,
): Promise<ExpoSqlitePersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
startSaveFirst? | boolean | Whether to start saving before loading, defaulting to false. |
| returns | Promise<ExpoSqlitePersister> | A Promise containing a reference to the |
This simply runs the startAutoLoad and startAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
This method can take initialContent to pass to the startAutoLoad method. See its documentation for more details.
If for some reason you want to start saving before you start loading, pass true as the second parameter.
Example
and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
persister.destroy();
Since
v6.1.0
stopAutoPersisting
The stopAutoPersist method is a convenience method that stops both automatic loading and saving of the Store data.
stopAutoPersisting(stopSaveFirst?: boolean): Promise<ExpoSqlitePersister>| Type | Description | |
|---|---|---|
stopSaveFirst? | boolean | Whether to stop saving before loading, defaulting to false. |
| returns | Promise<ExpoSqlitePersister> | A Promise containing a reference to the |
This simply runs the stopAutoLoad and stopAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
If for some reason you want to stop saving before you stop loading, pass true as the second parameter.
Example
automatically loading and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoPersisting();
console.log(persister.isAutoSaving());
// -> false
console.log(persister.isAutoLoading());
// -> false
persister.destroy();
Since
v6.1.0
Load methods
isAutoLoading
The isAutoLoading method lets you find out if the Persister is currently automatically loading its content.
isAutoLoading(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoLoading.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once.
load(initialContent?: Content | () => Content): Promise<ExpoSqlitePersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<ExpoSqlitePersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load method is called, data has previously been persisted and instead, that is loaded.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once, and then continuously.
startAutoLoad(initialContent?: Content | () => Content): Promise<ExpoSqlitePersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<ExpoSqlitePersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method first runs a single call to the load method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store.
This method is asynchronous because it starts by making a single call to the asynchronous load method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store, and loads data into it from the browser's session storage, which at first is empty (so the initialTables parameter is used). Subsequent changes to the underlying storage are then reflected in the Store (in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad method stops the automatic loading of data from storage previously started with the startAutoLoad method.
stopAutoLoad(): Promise<ExpoSqlitePersister>| returns | Promise<ExpoSqlitePersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically load, this method has no effect. This method is asynchronous.
Example
This example creates an empty Store, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
isAutoSaving
The isAutoSaving method lets you find out if the Persister is currently automatically saving its content.
isAutoSaving(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoSaving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save method takes data from the Store with which the Persister is associated and persists it into storage, once.
save(): Promise<ExpoSqlitePersister>| returns | Promise<ExpoSqlitePersister> | A Promise containing a reference to the |
|---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save method takes data from the Store with which the Persister is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<ExpoSqlitePersister>| returns | Promise<ExpoSqlitePersister> | A Promise containing a reference to the |
|---|
This method first runs a single call to the save method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave method stops the automatic save of data to storage previously started with the startAutoSave method.
stopAutoSave(): Promise<ExpoSqlitePersister>| returns | Promise<ExpoSqlitePersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically save, this method has no effect. This method is asynchronous.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
await persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
getStats
The getStats method provides a set of statistics about the Persister, and is used for debugging purposes.
getStats(): PersisterStats| returns | PersisterStats | A |
|---|
The PersisterStats object contains a count of the number of times the Persister has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister object. Remember that the startAutoLoad method invokes an explicit load when it starts, and the startAutoSave method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store and to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
createExpoSqlitePersister
The createExpoSqlitePersister function creates an ExpoSqlitePersister object that can persist the Store to a local Expo-SQLite database.
createExpoSqlitePersister(
store: Store | MergeableStore,
db: SQLiteDatabase,
configOrStoreTableName?: string | DatabasePersisterConfig,
onSqlCommand?: (sql: string, params?: any[]) => void,
onIgnoredError?: (error: any) => void,
): ExpoSqlitePersister| Type | Description | |
|---|---|---|
store | Store | MergeableStore | The |
db | SQLiteDatabase | The database instance that was returned from |
configOrStoreTableName? | string | DatabasePersisterConfig | A |
onSqlCommand? | (sql: string, params?: any[]) => void | An optional handler called every time the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | ExpoSqlitePersister | A reference to the new |
An ExpoSqlitePersister supports regular Store objects, and can also be used to persist the metadata of a MergeableStore when using the JSON serialization mode, as described below.
Note that this Persister is currently experimental as Expo themselves iterate on the underlying database library API.
As well as providing a reference to the Store to persist, you must provide a db parameter which identifies the database instance.
A database Persister uses one of two modes: either a JSON serialization of the whole Store stored in a single row of a table (the default), or a tabular mapping of Table Ids to database table names and vice-versa).
The third argument is a DatabasePersisterConfig object that configures which of those modes to use, and settings for each. If the third argument is simply a string, it is used as the storeTableName property of the JSON serialization.
See the documentation for the DpcJson and DpcTabular types for more information on how both of those modes can be configured.
Examples
This example creates a ExpoSqlitePersister object and persists the Store to a local SQLite database as a JSON serialization into the my_tinybase table. It makes a change to the database directly and then reloads it back into the Store.
import {openDatabaseSync} from 'expo-sqlite';
import {createStore} from 'tinybase';
import {createExpoSqlitePersister} from 'tinybase/persisters/persister-expo-sqlite';
const db = openDatabaseSync('my.db');
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createExpoSqlitePersister(store, db, 'my_tinybase');
await persister.save();
// Store will be saved to the database.
console.log(
await new Promise((resolve) =>
db.allAsync('SELECT * FROM my_tinybase;').then(resolve),
),
);
// -> [{_id: '_', store: '[{"pets":{"fido":{"species":"dog"}}},{}]'}]
await new Promise((resolve) =>
db
.allAsync(
'UPDATE my_tinybase SET store = ' +
`'[{"pets":{"felix":{"species":"cat"}}},{}]' WHERE _id = '_';`,
)
.then(resolve),
);
await persister.load();
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
await persister.destroy();
This example creates a ExpoSqlitePersister object and persists the Store to a local SQLite database with tabular mapping.
import {openDatabaseSync} from 'expo-sqlite';
import {createStore} from 'tinybase';
import {createExpoSqlitePersister} from 'tinybase/persisters/persister-expo-sqlite';
const db = openDatabaseSync('my.db');
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createExpoSqlitePersister(store, db, {
mode: 'tabular',
tables: {load: {pets: 'pets'}, save: {pets: 'pets'}},
});
await persister.save();
console.log(
await new Promise((resolve) =>
db.allAsync('SELECT * FROM pets;').then(resolve),
),
);
// -> [{_id: 'fido', species: 'dog'}]
await new Promise((resolve) =>
db
.allAsync(`INSERT INTO pets (_id, species) VALUES ('felix', 'cat')`)
.then(resolve),
);
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
await persister.destroy();
Since
v4.5.0
persister-file
The persister-file module of the TinyBase project lets you save and load Store data to and from a local file system (in an appropriate environment).
See also
Persistence guides
Since
v1.0.0
Interfaces
FilePersister
The FilePersister interface represents a Persister that lets you save and load Store data to and from a local file system.
You should use the createFilePersister function to create a FilePersister object.
It is a minor extension to the Persister interface and simply provides an extra getFilePath method for accessing the location of the local file the Store is being persisted to.
Since
v4.3.14
Getter methods
getStore
The getStore method returns a reference to the underlying Store or MergeableStore that is backing this Persister object.
getStore(): Store | MergeableStore| returns | Store | MergeableStore | A reference to the |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets its reference in order to update its data.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getFilePath
The getFilePath method returns the location of the local file the Store is being persisted to.
getFilePath(): string| returns | string | The location of the local file. |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets the file location back out again.
import {createStore} from 'tinybase';
import {createFilePersister} from 'tinybase/persisters/persister-file';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createFilePersister(store, '/app/persisted.json');
console.log(persister.getFilePath());
// -> '/app/persisted.json'
await persister.destroy();
Since
v4.3.14
Listener methods
addStatusListener
The addStatusListener method registers a listener function with the Persister that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<StoreOrMergeableStore>): string| Type | Description | |
|---|---|---|
listener | StatusListener<StoreOrMergeableStore> | The function that will be called whenever the |
| returns | string | A unique |
The provided listener is a StatusListener function, and will be called with a reference to the Persister and the new Status: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener method removes a listener that was previously added to the Persister.
delListener(listenerId: string): this| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | this | A reference to the |
Use the Id returned by whichever method was used to add the listener. Note that the Persister may re-use this Id for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
destroy
The destroy method should be called when this Persister object is no longer used.
destroy(): Promise<FilePersister>| returns | Promise<FilePersister> | A Promise containing a reference to the |
|---|
This guarantees that all of the listeners that the object registered with the underlying Store and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad method and the stopAutoSave method in succession. This method is asynchronous.
Example
This example creates a Store, associates a Persister object with it (that registers a TablesListener with the underlying Store), and then destroys it again, removing the listener.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
await persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus method lets you find out if the Persister is currently in the process of loading or saving content.
getStatus(): StatusIt can only be doing one or the other (or neither) at any given time. The Status enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister and queries its status.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<FilePersister>| Type | Description | |
|---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
| returns | Promise<FilePersister> | A reference to the |
For example, a database Persister may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister.
Example
This example creates a custom Persister object against a newly-created Store and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createStore} from 'tinybase';
import {createCustomPersister} from 'tinybase/persisters';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
() => checkRemoteSystemIsReady(),
() => sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
startAutoPersisting
The startAutoPersist method is a convenience method that starts both automatic loading and saving of the Store data.
startAutoPersisting(
initialContent?: Content | () => Content,
startSaveFirst?: boolean,
): Promise<FilePersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
startSaveFirst? | boolean | Whether to start saving before loading, defaulting to false. |
| returns | Promise<FilePersister> | A Promise containing a reference to the |
This simply runs the startAutoLoad and startAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
This method can take initialContent to pass to the startAutoLoad method. See its documentation for more details.
If for some reason you want to start saving before you start loading, pass true as the second parameter.
Example
and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
persister.destroy();
Since
v6.1.0
stopAutoPersisting
The stopAutoPersist method is a convenience method that stops both automatic loading and saving of the Store data.
stopAutoPersisting(stopSaveFirst?: boolean): Promise<FilePersister>| Type | Description | |
|---|---|---|
stopSaveFirst? | boolean | Whether to stop saving before loading, defaulting to false. |
| returns | Promise<FilePersister> | A Promise containing a reference to the |
This simply runs the stopAutoLoad and stopAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
If for some reason you want to stop saving before you stop loading, pass true as the second parameter.
Example
automatically loading and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoPersisting();
console.log(persister.isAutoSaving());
// -> false
console.log(persister.isAutoLoading());
// -> false
persister.destroy();
Since
v6.1.0
Load methods
isAutoLoading
The isAutoLoading method lets you find out if the Persister is currently automatically loading its content.
isAutoLoading(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoLoading.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once.
load(initialContent?: Content | () => Content): Promise<FilePersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<FilePersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load method is called, data has previously been persisted and instead, that is loaded.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once, and then continuously.
startAutoLoad(initialContent?: Content | () => Content): Promise<FilePersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<FilePersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method first runs a single call to the load method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store.
This method is asynchronous because it starts by making a single call to the asynchronous load method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store, and loads data into it from the browser's session storage, which at first is empty (so the initialTables parameter is used). Subsequent changes to the underlying storage are then reflected in the Store (in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad method stops the automatic loading of data from storage previously started with the startAutoLoad method.
stopAutoLoad(): Promise<FilePersister>| returns | Promise<FilePersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically load, this method has no effect. This method is asynchronous.
Example
This example creates an empty Store, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
isAutoSaving
The isAutoSaving method lets you find out if the Persister is currently automatically saving its content.
isAutoSaving(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoSaving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save method takes data from the Store with which the Persister is associated and persists it into storage, once.
save(): Promise<FilePersister>| returns | Promise<FilePersister> | A Promise containing a reference to the |
|---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save method takes data from the Store with which the Persister is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<FilePersister>| returns | Promise<FilePersister> | A Promise containing a reference to the |
|---|
This method first runs a single call to the save method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave method stops the automatic save of data to storage previously started with the startAutoSave method.
stopAutoSave(): Promise<FilePersister>| returns | Promise<FilePersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically save, this method has no effect. This method is asynchronous.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
await persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
getStats
The getStats method provides a set of statistics about the Persister, and is used for debugging purposes.
getStats(): PersisterStats| returns | PersisterStats | A |
|---|
The PersisterStats object contains a count of the number of times the Persister has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister object. Remember that the startAutoLoad method invokes an explicit load when it starts, and the startAutoSave method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store and to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
createFilePersister
The createFilePersister function creates a FilePersister object that can persist the Store to a local file.
createFilePersister(
store: Store | MergeableStore,
filePath: string,
onIgnoredError?: (error: any) => void,
): FilePersister| Type | Description | |
|---|---|---|
store | Store | MergeableStore | The |
filePath | string | The location of the local file to persist the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | FilePersister | A reference to the new |
A FilePersister supports both regular Store and MergeableStore objects.
As well as providing a reference to the Store to persist, you must provide a filePath parameter which identifies the file to persist it to.
Example
This example creates a FilePersister object and persists the Store to a local file.
import {createStore} from 'tinybase';
import {createFilePersister} from 'tinybase/persisters/persister-file';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createFilePersister(store, '/app/persisted.json');
await persister.save();
// Store JSON will be saved to the file.
await persister.load();
// Store JSON will be loaded from the file.
await persister.destroy();
Since
v1.0.0
persister-indexed-db
The persister-indexed-db module of the TinyBase project lets you save and load Store data to and from browser IndexedDB storage.
See also
Persistence guides
Since
v4.2.0
Interfaces
IndexedDbPersister
The IndexedDbPersister interface represents a Persister that lets you save and load Store data to and from browser IndexedDB storage.
You should use the createIndexedDbPersister function to create an IndexedDbPersister object.
It is a minor extension to the Persister interface and simply provides an extra getDbName method for accessing the unique key of the IndexedDB the Store is being persisted to.
Since
v4.3.14
Getter methods
getStore
The getStore method returns a reference to the underlying Store or MergeableStore that is backing this Persister object.
getStore(): Store| returns | Store | A reference to the |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets its reference in order to update its data.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getDbName
The getDbName method returns the unique key of the IndexedDB the Store is being persisted to.
getDbName(): string| returns | string | The unique key of the IndexedDB. |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets the unique key of the IndexedDB back out again.
import {createStore} from 'tinybase';
import {createIndexedDbPersister} from 'tinybase/persisters/persister-indexed-db';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createIndexedDbPersister(store, 'petStore');
console.log(persister.getDbName());
// -> 'petStore'
await persister.destroy();
Since
v4.3.14
Listener methods
addStatusListener
The addStatusListener method registers a listener function with the Persister that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<StoreOnly>): string| Type | Description | |
|---|---|---|
listener | StatusListener<StoreOnly> | The function that will be called whenever the |
| returns | string | A unique |
The provided listener is a StatusListener function, and will be called with a reference to the Persister and the new Status: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener method removes a listener that was previously added to the Persister.
delListener(listenerId: string): this| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | this | A reference to the |
Use the Id returned by whichever method was used to add the listener. Note that the Persister may re-use this Id for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
destroy
The destroy method should be called when this Persister object is no longer used.
destroy(): Promise<IndexedDbPersister>| returns | Promise<IndexedDbPersister> | A Promise containing a reference to the |
|---|
This guarantees that all of the listeners that the object registered with the underlying Store and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad method and the stopAutoSave method in succession. This method is asynchronous.
Example
This example creates a Store, associates a Persister object with it (that registers a TablesListener with the underlying Store), and then destroys it again, removing the listener.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
await persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus method lets you find out if the Persister is currently in the process of loading or saving content.
getStatus(): StatusIt can only be doing one or the other (or neither) at any given time. The Status enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister and queries its status.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<IndexedDbPersister>| Type | Description | |
|---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
| returns | Promise<IndexedDbPersister> | A reference to the |
For example, a database Persister may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister.
Example
This example creates a custom Persister object against a newly-created Store and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createStore} from 'tinybase';
import {createCustomPersister} from 'tinybase/persisters';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
() => checkRemoteSystemIsReady(),
() => sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
startAutoPersisting
The startAutoPersist method is a convenience method that starts both automatic loading and saving of the Store data.
startAutoPersisting(
initialContent?: Content | () => Content,
startSaveFirst?: boolean,
): Promise<IndexedDbPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
startSaveFirst? | boolean | Whether to start saving before loading, defaulting to false. |
| returns | Promise<IndexedDbPersister> | A Promise containing a reference to the |
This simply runs the startAutoLoad and startAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
This method can take initialContent to pass to the startAutoLoad method. See its documentation for more details.
If for some reason you want to start saving before you start loading, pass true as the second parameter.
Example
and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
persister.destroy();
Since
v6.1.0
stopAutoPersisting
The stopAutoPersist method is a convenience method that stops both automatic loading and saving of the Store data.
stopAutoPersisting(stopSaveFirst?: boolean): Promise<IndexedDbPersister>| Type | Description | |
|---|---|---|
stopSaveFirst? | boolean | Whether to stop saving before loading, defaulting to false. |
| returns | Promise<IndexedDbPersister> | A Promise containing a reference to the |
This simply runs the stopAutoLoad and stopAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
If for some reason you want to stop saving before you stop loading, pass true as the second parameter.
Example
automatically loading and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoPersisting();
console.log(persister.isAutoSaving());
// -> false
console.log(persister.isAutoLoading());
// -> false
persister.destroy();
Since
v6.1.0
Load methods
isAutoLoading
The isAutoLoading method lets you find out if the Persister is currently automatically loading its content.
isAutoLoading(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoLoading.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once.
load(initialContent?: Content | () => Content): Promise<IndexedDbPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<IndexedDbPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load method is called, data has previously been persisted and instead, that is loaded.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once, and then continuously.
startAutoLoad(initialContent?: Content | () => Content): Promise<IndexedDbPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<IndexedDbPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method first runs a single call to the load method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store.
This method is asynchronous because it starts by making a single call to the asynchronous load method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store, and loads data into it from the browser's session storage, which at first is empty (so the initialTables parameter is used). Subsequent changes to the underlying storage are then reflected in the Store (in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad method stops the automatic loading of data from storage previously started with the startAutoLoad method.
stopAutoLoad(): Promise<IndexedDbPersister>| returns | Promise<IndexedDbPersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically load, this method has no effect. This method is asynchronous.
Example
This example creates an empty Store, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
isAutoSaving
The isAutoSaving method lets you find out if the Persister is currently automatically saving its content.
isAutoSaving(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoSaving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save method takes data from the Store with which the Persister is associated and persists it into storage, once.
save(): Promise<IndexedDbPersister>| returns | Promise<IndexedDbPersister> | A Promise containing a reference to the |
|---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save method takes data from the Store with which the Persister is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<IndexedDbPersister>| returns | Promise<IndexedDbPersister> | A Promise containing a reference to the |
|---|
This method first runs a single call to the save method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave method stops the automatic save of data to storage previously started with the startAutoSave method.
stopAutoSave(): Promise<IndexedDbPersister>| returns | Promise<IndexedDbPersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically save, this method has no effect. This method is asynchronous.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
await persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
getStats
The getStats method provides a set of statistics about the Persister, and is used for debugging purposes.
getStats(): PersisterStats| returns | PersisterStats | A |
|---|
The PersisterStats object contains a count of the number of times the Persister has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister object. Remember that the startAutoLoad method invokes an explicit load when it starts, and the startAutoSave method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store and to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
createIndexedDbPersister
The createIndexedDbPersister function creates an IndexedDbPersister object that can persist a Store to the browser's IndexedDB storage.
createIndexedDbPersister(
store: Store,
dbName: string,
autoLoadIntervalSeconds?: number,
onIgnoredError?: (error: any) => void,
): IndexedDbPersister| Type | Description | |
|---|---|---|
store | Store | The |
dbName | string | The unique key to identify the IndexedDB to use. |
autoLoadIntervalSeconds? | number | How often to poll the database when in 'autoLoad' mode, defaulting to 1. |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | IndexedDbPersister | A reference to the new |
An IndexedDbPersister only supports regular Store objects, and cannot be used to persist the metadata of a MergeableStore.
As well as providing a reference to the Store to persist, you must provide a dbName parameter which is unique to your application. This is the key used to identify which IndexedDB to use.
Within that database, this Persister will create two object stores: one called 't', and one called 'v'. These will contain the Store's tabular and key-value data respectively, using 'k' and 'v' to store the key and value of each entry, as shown in the example.
Note that it is not possible to reactively detect changes to a browser's IndexedDB. If you do choose to enable automatic loading for the Persister (with the startAutoLoad method), it needs to poll the database for changes. The autoLoadIntervalSeconds method is used to indicate how often to do this.
Example
This example creates a IndexedDbPersister object and persists the Store to the browser's IndexedDB storage.
import {createStore} from 'tinybase';
import {createIndexedDbPersister} from 'tinybase/persisters/persister-indexed-db';
const store = createStore()
.setTable('pets', {fido: {species: 'dog'}})
.setTable('species', {dog: {price: 5}})
.setValues({open: true});
const persister = createIndexedDbPersister(store, 'petStore');
await persister.save();
// IndexedDB ->
// database petStore:
// objectStore t:
// object 0:
// k: "pets"
// v: {fido: {species: dog}}
// object 1:
// k: "species"
// v: {dog: {price: 5}}
// objectStore v:
// object 0:
// k: "open"
// v: true
await persister.destroy();
Since
v4.2.0
persister-libsql
The persister-libsql module of the TinyBase project lets you save and load Store data to and from a local LibSQL database (in an appropriate environment).
See also
Database Persistence guide
Since
v4.7.0
Interfaces
LibSqlPersister
The LibSqlPersister interface represents a Persister that lets you save and load Store data to and from a local LibSQL database.
You should use the createLibSqlPersister function to create a LibSqlPersister object.
It is a minor extension to the Persister interface and simply provides an extra getClient method for accessing a reference to the database client the Store is being persisted to.
Since
v4.7.0
Getter methods
getStore
The getStore method returns a reference to the underlying Store or MergeableStore that is backing this Persister object.
getStore(): Store| returns | Store | A reference to the |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets its reference in order to update its data.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getClient
The getClient method returns a reference to the database client the Store is being persisted to.
getClient(): Client| returns | Client | A reference to the database client. |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets the database client back out again.
import {createClient} from '@libsql/client';
import {createStore} from 'tinybase';
import {createLibSqlPersister} from 'tinybase/persisters/persister-libsql';
const client = createClient({url: 'file:my.db'});
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createLibSqlPersister(store, client, 'my_tinybase');
console.log(persister.getClient() == client);
// -> true
await persister.destroy();
Since
v4.7.0
Listener methods
addStatusListener
The addStatusListener method registers a listener function with the Persister that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<StoreOnly>): string| Type | Description | |
|---|---|---|
listener | StatusListener<StoreOnly> | The function that will be called whenever the |
| returns | string | A unique |
The provided listener is a StatusListener function, and will be called with a reference to the Persister and the new Status: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener method removes a listener that was previously added to the Persister.
delListener(listenerId: string): this| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | this | A reference to the |
Use the Id returned by whichever method was used to add the listener. Note that the Persister may re-use this Id for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
destroy
The destroy method should be called when this Persister object is no longer used.
destroy(): Promise<LibSqlPersister>| returns | Promise<LibSqlPersister> | A Promise containing a reference to the |
|---|
This guarantees that all of the listeners that the object registered with the underlying Store and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad method and the stopAutoSave method in succession. This method is asynchronous.
Example
This example creates a Store, associates a Persister object with it (that registers a TablesListener with the underlying Store), and then destroys it again, removing the listener.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
await persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus method lets you find out if the Persister is currently in the process of loading or saving content.
getStatus(): StatusIt can only be doing one or the other (or neither) at any given time. The Status enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister and queries its status.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<LibSqlPersister>| Type | Description | |
|---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
| returns | Promise<LibSqlPersister> | A reference to the |
For example, a database Persister may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister.
Example
This example creates a custom Persister object against a newly-created Store and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createStore} from 'tinybase';
import {createCustomPersister} from 'tinybase/persisters';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
() => checkRemoteSystemIsReady(),
() => sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
startAutoPersisting
The startAutoPersist method is a convenience method that starts both automatic loading and saving of the Store data.
startAutoPersisting(
initialContent?: Content | () => Content,
startSaveFirst?: boolean,
): Promise<LibSqlPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
startSaveFirst? | boolean | Whether to start saving before loading, defaulting to false. |
| returns | Promise<LibSqlPersister> | A Promise containing a reference to the |
This simply runs the startAutoLoad and startAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
This method can take initialContent to pass to the startAutoLoad method. See its documentation for more details.
If for some reason you want to start saving before you start loading, pass true as the second parameter.
Example
and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
persister.destroy();
Since
v6.1.0
stopAutoPersisting
The stopAutoPersist method is a convenience method that stops both automatic loading and saving of the Store data.
stopAutoPersisting(stopSaveFirst?: boolean): Promise<LibSqlPersister>| Type | Description | |
|---|---|---|
stopSaveFirst? | boolean | Whether to stop saving before loading, defaulting to false. |
| returns | Promise<LibSqlPersister> | A Promise containing a reference to the |
This simply runs the stopAutoLoad and stopAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
If for some reason you want to stop saving before you stop loading, pass true as the second parameter.
Example
automatically loading and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoPersisting();
console.log(persister.isAutoSaving());
// -> false
console.log(persister.isAutoLoading());
// -> false
persister.destroy();
Since
v6.1.0
Load methods
isAutoLoading
The isAutoLoading method lets you find out if the Persister is currently automatically loading its content.
isAutoLoading(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoLoading.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once.
load(initialContent?: Content | () => Content): Promise<LibSqlPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<LibSqlPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load method is called, data has previously been persisted and instead, that is loaded.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once, and then continuously.
startAutoLoad(initialContent?: Content | () => Content): Promise<LibSqlPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<LibSqlPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method first runs a single call to the load method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store.
This method is asynchronous because it starts by making a single call to the asynchronous load method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store, and loads data into it from the browser's session storage, which at first is empty (so the initialTables parameter is used). Subsequent changes to the underlying storage are then reflected in the Store (in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad method stops the automatic loading of data from storage previously started with the startAutoLoad method.
stopAutoLoad(): Promise<LibSqlPersister>| returns | Promise<LibSqlPersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically load, this method has no effect. This method is asynchronous.
Example
This example creates an empty Store, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
isAutoSaving
The isAutoSaving method lets you find out if the Persister is currently automatically saving its content.
isAutoSaving(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoSaving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save method takes data from the Store with which the Persister is associated and persists it into storage, once.
save(): Promise<LibSqlPersister>| returns | Promise<LibSqlPersister> | A Promise containing a reference to the |
|---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save method takes data from the Store with which the Persister is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<LibSqlPersister>| returns | Promise<LibSqlPersister> | A Promise containing a reference to the |
|---|
This method first runs a single call to the save method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave method stops the automatic save of data to storage previously started with the startAutoSave method.
stopAutoSave(): Promise<LibSqlPersister>| returns | Promise<LibSqlPersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically save, this method has no effect. This method is asynchronous.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
await persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
getStats
The getStats method provides a set of statistics about the Persister, and is used for debugging purposes.
getStats(): PersisterStats| returns | PersisterStats | A |
|---|
The PersisterStats object contains a count of the number of times the Persister has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister object. Remember that the startAutoLoad method invokes an explicit load when it starts, and the startAutoSave method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store and to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
createLibSqlPersister
The createLibSqlPersister function creates a LibSqlPersister object that can persist a Store to a local LibSQL database.
createLibSqlPersister(
store: Store,
client: Client,
configOrStoreTableName?: string | DatabasePersisterConfig,
onSqlCommand?: (sql: string, params?: any[]) => void,
onIgnoredError?: (error: any) => void,
): LibSqlPersister| Type | Description | |
|---|---|---|
store | Store | The |
client | Client | The database client that was returned from |
configOrStoreTableName? | string | DatabasePersisterConfig | A |
onSqlCommand? | (sql: string, params?: any[]) => void | An optional handler called every time the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | LibSqlPersister | A reference to the new |
A LibSqlPersister only supports regular Store objects, and cannot be used to persist the metadata of a MergeableStore.
As well as providing a reference to the Store to persist, you must provide a client parameter which identifies the database client.
A database Persister uses one of two modes: either a JSON serialization of the whole Store stored in a single row of a table (the default), or a tabular mapping of Table Ids to database table names and vice-versa).
The third argument is a DatabasePersisterConfig object that configures which of those modes to use, and settings for each. If the third argument is simply a string, it is used as the storeTableName property of the JSON serialization.
See the documentation for the DpcJson and DpcTabular types for more information on how both of those modes can be configured.
Examples
This example creates a LibSqlPersister object and persists the Store to a local SQLite database as a JSON serialization into the my_tinybase table. It makes a change to the database directly and then reloads it back into the Store.
import {createClient} from '@libsql/client';
import {createStore} from 'tinybase';
import {createLibSqlPersister} from 'tinybase/persisters/persister-libsql';
const client = createClient({url: 'file:my.db'});
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createLibSqlPersister(store, client, 'my_tinybase');
await persister.save();
// Store will be saved to the database.
console.log((await client.execute('SELECT * FROM my_tinybase;')).rows);
// -> [{_id: '_', store: '[{"pets":{"fido":{"species":"dog"}}},{}]'}]
await client.execute(
'UPDATE my_tinybase SET store = ' +
`'[{"pets":{"felix":{"species":"cat"}}},{}]' WHERE _id = '_';`,
);
await persister.load();
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
await persister.destroy();
This example creates a LibSqlPersister object and persists the Store to a local SQLite database with tabular mapping.
import {createClient} from '@libsql/client';
import {createStore} from 'tinybase';
import {createLibSqlPersister} from 'tinybase/persisters/persister-libsql';
const client = createClient({url: 'file:my.db'});
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createLibSqlPersister(store, client, {
mode: 'tabular',
tables: {load: {pets: 'pets'}, save: {pets: 'pets'}},
});
await persister.save();
console.log((await client.execute('SELECT * FROM pets;')).rows);
// -> [{_id: 'fido', species: 'dog'}]
await client.execute(
`INSERT INTO pets (_id, species) VALUES ('felix', 'cat')`,
);
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
await persister.destroy();
Since
v4.7.0
persister-partykit-client
The persister-partykit-client module of the TinyBase project contains the client portion of the PartyKit integration.
It contains a Persister which, when run in a PartyKit client environment, lets you save and load Store data from the client to the durable PartyKit cloud storage of a server (that is using the complementary persister-partykit-server module).
This enables synchronization of the same Store across multiple clients in a PartyKit party room.
Note that both the client and server parts of this Persister are currently experimental as PartyKit is new and still maturing.
See also
Persistence guides
Since
v4.3.0
Interfaces
PartyKitPersister
The PartyKitPersister interface represents a Persister that lets you save and load Store data from the client to the durable PartyKit cloud storage of a server.
You should use the createPartyKitPersister function to create a PartyKitPersister object.
It is a minor extension to the Persister interface and simply provides an extra getConnection method for accessing the PartySocket the Store is being persisted to.
Since
v4.3.14
Getter methods
getStore
The getStore method returns a reference to the underlying Store or MergeableStore that is backing this Persister object.
getStore(): Store| returns | Store | A reference to the |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets its reference in order to update its data.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getConnection
The getConnection method returns the PartySocket the Store is being persisted to.
getConnection(): PartySocket| returns | PartySocket | The PartySocket. |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets the PartySocket back out again.
import {PartySocket} from 'partysocket';
import {createStore} from 'tinybase';
import {createPartyKitPersister} from 'tinybase/persisters/persister-partykit-client';
const partySocket = new PartySocket({
host: '127.0.0.1:1999',
room: 'my_room',
});
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createPartyKitPersister(store, partySocket);
console.log(persister.getConnection() == partySocket);
// -> true
await persister.destroy();
Since
v4.3.14
Listener methods
addStatusListener
The addStatusListener method registers a listener function with the Persister that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<StoreOnly>): string| Type | Description | |
|---|---|---|
listener | StatusListener<StoreOnly> | The function that will be called whenever the |
| returns | string | A unique |
The provided listener is a StatusListener function, and will be called with a reference to the Persister and the new Status: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener method removes a listener that was previously added to the Persister.
delListener(listenerId: string): this| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | this | A reference to the |
Use the Id returned by whichever method was used to add the listener. Note that the Persister may re-use this Id for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
destroy
The destroy method should be called when this Persister object is no longer used.
destroy(): Promise<PartyKitPersister>| returns | Promise<PartyKitPersister> | A Promise containing a reference to the |
|---|
This guarantees that all of the listeners that the object registered with the underlying Store and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad method and the stopAutoSave method in succession. This method is asynchronous.
Example
This example creates a Store, associates a Persister object with it (that registers a TablesListener with the underlying Store), and then destroys it again, removing the listener.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
await persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus method lets you find out if the Persister is currently in the process of loading or saving content.
getStatus(): StatusIt can only be doing one or the other (or neither) at any given time. The Status enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister and queries its status.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<PartyKitPersister>| Type | Description | |
|---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
| returns | Promise<PartyKitPersister> | A reference to the |
For example, a database Persister may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister.
Example
This example creates a custom Persister object against a newly-created Store and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createStore} from 'tinybase';
import {createCustomPersister} from 'tinybase/persisters';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
() => checkRemoteSystemIsReady(),
() => sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
startAutoPersisting
The startAutoPersist method is a convenience method that starts both automatic loading and saving of the Store data.
startAutoPersisting(
initialContent?: Content | () => Content,
startSaveFirst?: boolean,
): Promise<PartyKitPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
startSaveFirst? | boolean | Whether to start saving before loading, defaulting to false. |
| returns | Promise<PartyKitPersister> | A Promise containing a reference to the |
This simply runs the startAutoLoad and startAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
This method can take initialContent to pass to the startAutoLoad method. See its documentation for more details.
If for some reason you want to start saving before you start loading, pass true as the second parameter.
Example
and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
persister.destroy();
Since
v6.1.0
stopAutoPersisting
The stopAutoPersist method is a convenience method that stops both automatic loading and saving of the Store data.
stopAutoPersisting(stopSaveFirst?: boolean): Promise<PartyKitPersister>| Type | Description | |
|---|---|---|
stopSaveFirst? | boolean | Whether to stop saving before loading, defaulting to false. |
| returns | Promise<PartyKitPersister> | A Promise containing a reference to the |
This simply runs the stopAutoLoad and stopAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
If for some reason you want to stop saving before you stop loading, pass true as the second parameter.
Example
automatically loading and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoPersisting();
console.log(persister.isAutoSaving());
// -> false
console.log(persister.isAutoLoading());
// -> false
persister.destroy();
Since
v6.1.0
Load methods
isAutoLoading
The isAutoLoading method lets you find out if the Persister is currently automatically loading its content.
isAutoLoading(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoLoading.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once.
load(initialContent?: Content | () => Content): Promise<PartyKitPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<PartyKitPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load method is called, data has previously been persisted and instead, that is loaded.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once, and then continuously.
startAutoLoad(initialContent?: Content | () => Content): Promise<PartyKitPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<PartyKitPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method first runs a single call to the load method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store.
This method is asynchronous because it starts by making a single call to the asynchronous load method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store, and loads data into it from the browser's session storage, which at first is empty (so the initialTables parameter is used). Subsequent changes to the underlying storage are then reflected in the Store (in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad method stops the automatic loading of data from storage previously started with the startAutoLoad method.
stopAutoLoad(): Promise<PartyKitPersister>| returns | Promise<PartyKitPersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically load, this method has no effect. This method is asynchronous.
Example
This example creates an empty Store, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
isAutoSaving
The isAutoSaving method lets you find out if the Persister is currently automatically saving its content.
isAutoSaving(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoSaving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save method takes data from the Store with which the Persister is associated and persists it into storage, once.
save(): Promise<PartyKitPersister>| returns | Promise<PartyKitPersister> | A Promise containing a reference to the |
|---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save method takes data from the Store with which the Persister is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<PartyKitPersister>| returns | Promise<PartyKitPersister> | A Promise containing a reference to the |
|---|
This method first runs a single call to the save method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave method stops the automatic save of data to storage previously started with the startAutoSave method.
stopAutoSave(): Promise<PartyKitPersister>| returns | Promise<PartyKitPersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically save, this method has no effect. This method is asynchronous.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
await persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
getStats
The getStats method provides a set of statistics about the Persister, and is used for debugging purposes.
getStats(): PersisterStats| returns | PersisterStats | A |
|---|
The PersisterStats object contains a count of the number of times the Persister has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister object. Remember that the startAutoLoad method invokes an explicit load when it starts, and the startAutoSave method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store and to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
createPartyKitPersister
The createPartyKitPersister function creates a PartyKitPersister object that can persist the Store to durable PartyKit storage, enabling synchronization of the same Store across multiple clients.
createPartyKitPersister(
store: Store,
connection: PartySocket,
configOrStoreProtocol?: PartyKitPersisterConfig | "http" | "https",
onIgnoredError?: (error: any) => void,
): PartyKitPersister| Type | Description | |
|---|---|---|
store | Store | The |
connection | PartySocket | The PartySocket to use for participating in the PartyKit room. |
configOrStoreProtocol? | PartyKitPersisterConfig | "http" | "https" | The |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | PartyKitPersister | A reference to the new |
A PartyKitPersister only supports regular Store objects, and cannot be used to persist the metadata of a MergeableStore.
As well as providing a reference to the Store to persist, you must provide a connection parameter which is a PartyKit PartySocket that you have already instantiated with details of the host and room.
All suitably-equipped TinyBase clients connecting to that room will get to share synchronized Store state.
The server room's Store is considered the source of truth. If it is a newly-created room, then calling the save method on this Persister will initiate it. If, however, there is already a Store present on the server, the save method will fail gracefully.
In general, you are strongly recommended to use the auto-save and auto-load functionality to stay in sync incrementally with the server, as per the example below. This pattern will handle newly-created servers and newly-created clients - and the synchronization involved in joining rooms, leaving them, or temporarily going offline.
See the PartyKit client socket API documentation for more details.
Example
This example creates a PartyKitPersister object and persists the Store to the browser's IndexedDB storage.
import {PartySocket} from 'partysocket';
import {createStore} from 'tinybase';
import {createPartyKitPersister} from 'tinybase/persisters/persister-partykit-client';
const store = createStore()
.setTable('pets', {fido: {species: 'dog'}})
.setTable('species', {dog: {price: 5}})
.setValues({open: true});
const partySocket = new PartySocket({
host: '127.0.0.1:1999',
room: 'my_room',
});
const persister = createPartyKitPersister(store, partySocket);
await persister.startAutoLoad();
await persister.startAutoSave();
// Store will now be synchronized with the room.
await persister.destroy();
Since
v4.3.0
Type Aliases
PartyKitPersisterConfig
The PartyKitPersisterConfig type describes the configuration of a PartyKit Persister on the client side.
{
storeProtocol?: "http" | "https";
storePath?: string;
messagePrefix?: string;
}| Type | Description | |
|---|---|---|
storeProtocol? | "http" | "https" | The HTTP protocol to use (in addition to the websocket channel). This defaults to 'https' but you may wish to use 'http' for local PartyKit development. |
storePath? | string | The path used to set and get the whole |
messagePrefix? | string | The prefix at the beginning of the web socket messages sent between the client and the server when synchronizing the |
The defaults (if used on both the server and client) will work fine, but if you are building more complex PartyKit apps and you need to configure path names, for example, then this is the thing to use.
Example
When applied to a PartyKit Persister, this PartyKitPersisterConfig will load and save a JSON serialization from and to an end point in your room called /my_tinybase, and use HTTP (rather than the default HTTPS) as the protocol.
Note that this would require you to also add the matching storePath setting to the TinyBasePartyKitServerConfig on the server side.
export const partyKitPersisterConfig = {
storeProtocol: 'http',
storePath: '/my_tinybase',
};
Since
v4.3.9
persister-partykit-server
The persister-partykit-server module of the TinyBase project contains the server portion of the PartyKit integration.
It contains a class which, when run in a PartyKit server environment, lets you save and load Store data from a client (that is using the complementary persister-partykit-client module) to durable PartyKit cloud storage.
This enables synchronization of the same Store across multiple clients in a PartyKit party room.
Note that both the client and server parts of this Persister are currently experimental as PartyKit is new and still maturing.
See also
Persistence guides
Since
v4.3.0
Classes
TinyBasePartyKitServer
This extends the PartyKit Server class, which provides a selection of methods you are expected to implement. The TinyBasePartyKitServer implements only two of them, the onMessage method and the onRequest method, as well as the constructor.
If you wish to use TinyBasePartyKitServer as a general PartyKit server, you can implement other methods. But you must remember to call the super implementations of those methods to ensure the TinyBase synchronization stays supported in addition to your own custom functionality. The same applies to the constructor if you choose to implement that.
import {TinyBasePartyKitServer} from 'tinybase/persisters/persister-partykit-server';
// This is your PartyKit server entry point.
export class MyServer extends TinyBasePartyKitServer {
constructor(party) {
super(party);
// custom constructor code
}
async onStart() {
// no need to call super.onStart()
console.log('Server started');
}
async onMessage(message, connection) {
await super.onMessage(message, connection);
// custom onMessage code
}
async onRequest(request) {
// custom onRequest code, else:
return await super.onRequest(request);
}
}
See the PartyKit server API documentation for more details.
Since
v4.3.0
Constructors
constructor
The constructor is used to create the server.
new TinyBasePartyKitServer(party: Room): TinyBasePartyKitServer| Type | Description | |
|---|---|---|
party | Room | |
| returns | TinyBasePartyKitServer | A new instance of the |
Since
v4.3.9
Methods
Connection methods
onMessage
The onMessage method is called when the server receives a message from a client.
onMessage(
message: string,
connection: Connection,
): Promise<void>| Type | Description | |
|---|---|---|
message | string | |
connection | Connection | |
| returns | Promise<void> |
If you choose to implement additional functionality in this method, you must remember to call the super implementation to ensure the TinyBase synchronization stays supported.
import {TinyBasePartyKitServer} from 'tinybase/persisters/persister-partykit-server';
export class MyServer extends TinyBasePartyKitServer {
async onMessage(message, connection) {
await super.onMessage(message, connection);
// custom onMessage code
}
}
See the PartyKit server API documentation for more details.
Since
v4.3.0
onRequest
The onRequest method is called when a HTTP request is made to the party URL.
onRequest(request: Request): Promise<Response>| Type | Description | |
|---|---|---|
request | Request | |
| returns | Promise<Response> |
If you choose to implement additional functionality in this method, you must remember to call the super implementation to ensure the TinyBase synchronization stays supported.
import {TinyBasePartyKitServer} from 'tinybase/persisters/persister-partykit-server';
export class MyServer extends TinyBasePartyKitServer {
async onRequest(request) {
// custom onRequest code, else:
return await super.onRequest(request);
}
}
See the PartyKit server API documentation for more details.
Since
v4.3.0
Sanitization methods
canDelTable
The canDelTable method lets you allow or disallow deletions of a Table stored on the server, as sent from a client.
canDelTable(
tableId: string,
connection: Connection,
): Promise<boolean>| Type | Description | |
|---|---|---|
tableId | string | |
connection | Connection | |
| returns | Promise<boolean> |
This is one of the functions use to sanitize the data that is being sent from a client. Perhaps you might want to make sure the server-stored data adheres to a particular schema, or you might want to make certain data read-only. Remember that you cannot trust the client to only send data that the server considers valid or safe.
This method is passed the Table Id that the client is trying to delete. The connection parameter will be the web socket connection of that client. You can, for instance, use this to distinguish between different users.
Return false from this method to disallow this Table from being deleted on the server, or true to allow it. The default implementation returns true to allow deletion.
Example
The following implementation will strip out any attempts by the client to delete the 'user' Table:
import {TinyBasePartyKitServer} from 'tinybase/persisters/persister-partykit-server';
export class MyServer extends TinyBasePartyKitServer {
canDelTable(tableId) {
return tableId != 'user';
}
}
Since
v4.3.12
canSetTable
The canSetTable method lets you allow or disallow any changes to a Table stored on the server, as sent from a client.
canSetTable(
tableId: string,
initialSave: boolean,
requestOrConnection: Request | Connection,
): Promise<boolean>| Type | Description | |
|---|---|---|
tableId | string | |
initialSave | boolean | |
requestOrConnection | Request | Connection | |
| returns | Promise<boolean> |
This is one of the functions use to sanitize the data that is being sent from a client. Perhaps you might want to make sure the server-stored data adheres to a particular schema, or you might want to make certain data read-only. Remember that you cannot trust the client to only send data that the server considers valid or safe.
This method is passed the Table Id that the client is trying to change. The initialSave parameter distinguishes between the first bulk save of the Store to the PartyKit room over HTTP (true), and subsequent incremental updates over a web sockets (false).
The requestOrConnection parameter will either be the HTTP(S) request or the web socket connection, in those two cases respectively. You can, for instance, use this to distinguish between different users.
Since v4.3.13, the final parameter is the Cell previously stored on the server, if any. Use this to distinguish between the addition of a new Cell (in which case it will be undefined) and the updating of an existing one.
Return false from this method to disallow changes to this Table on the server, or true to allow them (subject to subsequent canSetRow method, canDelRow method, canSetCell method, and canSetCell method checks). The default implementation returns true to allow all changes.
Example
The following implementation will strip out any attempts by the client to update any 'user' tabular data after the initial save:
import {TinyBasePartyKitServer} from 'tinybase/persisters/persister-partykit-server';
export class MyServer extends TinyBasePartyKitServer {
canSetTable(tableId, initialSave) {
return initialSave || tableId != 'user';
}
}
Since
v4.3.12
canDelRow
The canDelRow method lets you allow or disallow deletions of a Row stored on the server, as sent from a client.
canDelRow(
tableId: string,
rowId: string,
connection: Connection,
): Promise<boolean>| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
connection | Connection | |
| returns | Promise<boolean> |
This is one of the functions use to sanitize the data that is being sent from a client. Perhaps you might want to make sure the server-stored data adheres to a particular schema, or you might want to make certain data read-only. Remember that you cannot trust the client to only send data that the server considers valid or safe.
This method is passed the Table Id and Row Id that the client is trying to delete. The connection parameter will be the web socket connection of that client. You can, for instance, use this to distinguish between different users.
Return false from this method to disallow this Row from being deleted on the server, or true to allow it. The default implementation returns true to allow deletion.
Example
The following implementation will strip out any attempts by the client to delete the 'me' Row of the 'user' Table:
import {TinyBasePartyKitServer} from 'tinybase/persisters/persister-partykit-server';
export class MyServer extends TinyBasePartyKitServer {
canDelRow(tableId, rowId) {
return tableId != 'user' || rowId != 'me';
}
}
Since
v4.3.12
canSetRow
The canSetRow method lets you allow or disallow any changes to a Row stored on the server, as sent from a client.
canSetRow(
tableId: string,
rowId: string,
initialSave: boolean,
requestOrConnection: Request | Connection,
): Promise<boolean>| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
initialSave | boolean | |
requestOrConnection | Request | Connection | |
| returns | Promise<boolean> |
This is one of the functions use to sanitize the data that is being sent from a client. Perhaps you might want to make sure the server-stored data adheres to a particular schema, or you might want to make certain data read-only. Remember that you cannot trust the client to only send data that the server considers valid or safe.
This method is passed the Table Id and Row Id that the client is trying to change. The initialSave parameter distinguishes between the first bulk save of the Store to the PartyKit room over HTTP (true), and subsequent incremental updates over a web sockets (false).
The final requestOrConnection parameter will either be the HTTP(S) request or the web socket connection, in those two cases respectively. You can, for instance, use this to distinguish between different users.
Return false from this method to disallow changes to this Row on the server, or true to allow them (subject to subsequent canSetCell method and canSetCell method checks). The default implementation returns true to allow all changes.
Example
The following implementation will strip out any attempts by the client to update the 'me' Row of the 'user' Table after the initial save:
import {TinyBasePartyKitServer} from 'tinybase/persisters/persister-partykit-server';
export class MyServer extends TinyBasePartyKitServer {
canSetRow(tableId, rowId, initialSave) {
return initialSave || tableId != 'user' || rowId != 'me';
}
}
Since
v4.3.12
canDelCell
The canDelCell method lets you allow or disallow deletions of a Cell stored on the server, as sent from a client.
canDelCell(
tableId: string,
rowId: string,
cellId: string,
connection: Connection,
): Promise<boolean>| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
connection | Connection | |
| returns | Promise<boolean> |
This is one of the functions use to sanitize the data that is being sent from a client. Perhaps you might want to make sure the server-stored data adheres to a particular schema, or you might want to make certain data read-only. Remember that you cannot trust the client to only send data that the server considers valid or safe.
This method is passed the Table Id, Row Id, and Cell Id that the client is trying to delete. The connection parameter will be the web socket connection of that client. You can, for instance, use this to distinguish between different users.
Return false from this method to disallow this Cell from being deleted on the server, or true to allow it. The default implementation returns true to allow deletion.
Example
The following implementation will strip out any attempts by the client to delete the 'name' Cell of the 'me' Row of the 'user' Table:
import {TinyBasePartyKitServer} from 'tinybase/persisters/persister-partykit-server';
export class MyServer extends TinyBasePartyKitServer {
canDelCell(tableId, rowId, cellId) {
return tableId != 'user' || rowId != 'me' || cellId != 'name';
}
}
Since
v4.3.12
canSetCell
The canSetCell method lets you allow or disallow any changes to a Cell stored on the server, as sent from a client.
canSetCell(
tableId: string,
rowId: string,
cellId: string,
cell: Cell,
initialSave: boolean,
requestOrConnection: Request | Connection,
oldCell: CellOrUndefined,
): Promise<boolean>| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
cell | Cell | |
initialSave | boolean | |
requestOrConnection | Request | Connection | |
oldCell | CellOrUndefined | |
| returns | Promise<boolean> |
This is one of the functions use to sanitize the data that is being sent from a client. Perhaps you might want to make sure the server-stored data adheres to a particular schema, or you might want to make certain data read-only. Remember that you cannot trust the client to only send data that the server considers valid or safe.
This method is passed the Table Id, Row Id, and Cell Id that the client is trying to change - as well as the Cell value itself. The initialSave parameter distinguishes between the first bulk save of the Store to the PartyKit room over HTTP (true), and subsequent incremental updates over a web sockets (false).
The final requestOrConnection parameter will either be the HTTP(S) request or the web socket connection, in those two cases respectively. You can, for instance, use this to distinguish between different users.
Return false from this method to disallow changes to this Cell on the server, or true to allow them. The default implementation returns true to allow all changes.
Example
The following implementation will strip out any attempts by the client to update the 'name' Cell of the 'me' Row of the 'user' Table after the initial save:
import {TinyBasePartyKitServer} from 'tinybase/persisters/persister-partykit-server';
export class MyServer extends TinyBasePartyKitServer {
canSetCell(tableId, rowId, cellId, cell, initialSave) {
return (
initialSave || tableId != 'user' || rowId != 'me' || cellId != 'name'
);
}
}
Since
v4.3.12
canDelValue
The canDelValue method lets you allow or disallow deletions of a Value stored on the server, as sent from a client.
canDelValue(
valueId: string,
connection: Connection,
): Promise<boolean>| Type | Description | |
|---|---|---|
valueId | string | |
connection | Connection | |
| returns | Promise<boolean> |
This is one of the functions use to sanitize the data that is being sent from a client. Perhaps you might want to make sure the server-stored data adheres to a particular schema, or you might want to make certain data read-only. Remember that you cannot trust the client to only send data that the server considers valid or safe.
This method is passed the Value Id that the client is trying to delete. The connection parameter will be the web socket connection of that client. You can, for instance, use this to distinguish between different users.
Return false from this method to disallow this Value from being deleted on the server, or true to allow it. The default implementation returns true to allow deletion.
Example
The following implementation will strip out any attempts by the client to delete the 'userId' Value:
import {TinyBasePartyKitServer} from 'tinybase/persisters/persister-partykit-server';
export class MyServer extends TinyBasePartyKitServer {
canDelValue(valueId) {
return valueId != 'userId';
}
}
Since
v4.3.12
canSetValue
The canSetValue method lets you allow or disallow any changes to a Value stored on the server, as sent from a client.
canSetValue(
valueId: string,
value: Value,
initialSave: boolean,
requestOrConnection: Request | Connection,
oldValue: ValueOrUndefined,
): Promise<boolean>| Type | Description | |
|---|---|---|
valueId | string | |
value | Value | |
initialSave | boolean | |
requestOrConnection | Request | Connection | |
oldValue | ValueOrUndefined | |
| returns | Promise<boolean> |
This is one of the functions use to sanitize the data that is being sent from a client. Perhaps you might want to make sure the server-stored data adheres to a particular schema, or you might want to make certain data read-only. Remember that you cannot trust the client to only send data that the server considers valid or safe.
This method is passed the Value Id that the client is trying to change - as well as the Value itself. The initialSave parameter distinguishes between the first bulk save of the Store to the PartyKit room over HTTP (true), and subsequent incremental updates over a web sockets (false).
The requestOrConnection parameter will either be the HTTP(S) request or the web socket connection, in those two cases respectively. You can, for instance, use this to distinguish between different users.
Since v4.3.13, the final parameter is the Value previously stored on the server, if any. Use this to distinguish between the addition of a new Value (in which case it will be undefined) and the updating of an existing one.
Return false from this method to disallow changes to this Value on the server, or true to allow them. The default implementation returns true to allow all changes.
Example
The following implementation will strip out any attempts by the client to update the 'userId' Value after the initial save:
import {TinyBasePartyKitServer} from 'tinybase/persisters/persister-partykit-server';
export class MyServer extends TinyBasePartyKitServer {
canSetValue(valueId, value, initialSave) {
return initialSave || valueId != 'userId';
}
}
Since
v4.3.12
Properties
config
The config property is used to optionally configure the server, using an object of the TinyBasePartyKitServerConfig type.
TinyBasePartyKitServerConfigSee the documentation for that type for more details.
Since
v4.3.9
Functions
Connection functions
broadcastChanges
The broadcastChanges function allows you to broadcast Store changes to all the client connections of a TinyBasePartyKitServer.
broadcastChanges(
server: TinyBasePartyKitServer,
changes: Changes,
without?: string[],
): Promise<void>| Type | Description | |
|---|---|---|
server | TinyBasePartyKitServer | A reference to the |
changes | Changes | The |
without? | string[] | An optional array of client connection |
| returns | Promise<void> |
This is intended for specialist applications that require the ability to update clients of a TinyBasePartyKitServer in their own custom ways.
The function is asynchronous, so you should use the await keyword or handle its completion as a promise.
Since
v4.5.1
Storage functions
hasStoreInStorage
The hasStoreInStorage function returns a boolean indicating whether durable PartyKit storage contains a serialized Store.
hasStoreInStorage(
storage: Storage,
storagePrefix?: string,
): Promise<boolean>| Type | Description | |
|---|---|---|
storage | Storage | A reference to the storage object, as would normally be accessible from the |
storagePrefix? | string | An optional prefix used before all the keys in the server's durable storage, to match the equivalent property in the server's |
| returns | Promise<boolean> | A promised boolean indicating whether a |
This is intended for specialist applications that require the ability to inspect or load a TinyBase Store from a server's storage outside of the normal context of a TinyBasePartyKitServer.
The function is asynchronous, so you should use the await keyword or handle the result as a promise.
Since
v4.4.1
loadStoreFromStorage
The loadStoreFromStorage function returns the content of a Store from durable PartyKit storage.
loadStoreFromStorage(
storage: Storage,
storagePrefix?: string,
): Promise<Content>| Type | Description | |
|---|---|---|
storage | Storage | A reference to the storage object, as would normally be accessible from the |
storagePrefix? | string | An optional prefix used before all the keys in the server's durable storage, to match the equivalent property in the server's |
| returns | Promise<Content> |
This is intended for specialist applications that require the ability to inspect or load a TinyBase Store from a server's storage outside of the normal context of a TinyBasePartyKitServer.
The function is asynchronous, so you should use the await keyword or handle the result as a promise.
Since
v4.4.1
Type Aliases
TinyBasePartyKitServerConfig
The TinyBasePartyKitServerConfig type describes the configuration of a PartyKit Persister on the server side.
{
storePath?: string;
messagePrefix?: string;
storagePrefix?: string;
responseHeaders?: HeadersInit;
}| Type | Description | |
|---|---|---|
storePath? | string | The path used to set and get the whole |
messagePrefix? | string | The prefix at the beginning of the web socket messages between the client and the server when synchronizing the |
storagePrefix? | string | The prefix used before all the keys in the server's durable storage. Use this in case you are worried about the |
responseHeaders? | HeadersInit | An object containing the extra HTTP(S) headers returned to the client from this server. This defaults to the following three headers to allow CORS. |
The defaults (if used on both the server and client) will work fine, but if you are building more complex PartyKit apps and you need to configure path names, for example, then this is the type to use.
Example
When set as the config in a TinyBasePartyKitServer, this TinyBasePartyKitServerConfig will expect clients to load and save their JSON serialization from and to an end point in the room called /my_tinybase. Note that this would require you to also add the matching storePath setting to the PartyKitPersisterConfig on the client side.
It will also store the data in the durable storage with a prefix of 'tinybase_' in case you are worried about colliding with other data stored in the room.
import {TinyBasePartyKitServer} from 'tinybase/persisters/persister-partykit-server';
export class MyServer extends TinyBasePartyKitServer {
readonly config = {
storePath: '/my_tinybase',
storagePrefix: 'tinybase_',
};
}
Since
v4.3.9
persister-pglite
The persister-pglite module of the TinyBase project lets you save and load Store data to and from a PGlite database (in an appropriate environment).
See also
Database Persistence guide
Since
5.2.0
Interfaces
PglitePersister
The PglitePersister interface represents a Persister that lets you save and load Store data to and from a local PGlite database.
You should use the createPglitePersister function to create a PglitePersister object.
It is a minor extension to the Persister interface and simply provides an extra getPglite method for accessing a reference to the database connection the Store is being persisted to.
Since
5.2.0
Getter methods
getStore
The getStore method returns a reference to the underlying Store or MergeableStore that is backing this Persister object.
getStore(): Store | MergeableStore| returns | Store | MergeableStore | A reference to the |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets its reference in order to update its data.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getPglite
The getPglite method returns a reference to the database connection the Store is being persisted to.
getPglite(): PGlite| returns | PGlite | A reference to the database connection. |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets the database connection back out again.
import {PGlite} from '@electric-sql/pglite';
import {createStore} from 'tinybase';
import {createPglitePersister} from 'tinybase/persisters/persister-pglite';
const pglite = await PGlite.create();
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = await createPglitePersister(
store,
pglite,
'my_tinybase',
);
console.log(persister.getPglite() == pglite);
// -> true
await persister.destroy();
await pglite.close();
Since
5.2.0
Listener methods
addStatusListener
The addStatusListener method registers a listener function with the Persister that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<StoreOrMergeableStore>): string| Type | Description | |
|---|---|---|
listener | StatusListener<StoreOrMergeableStore> | The function that will be called whenever the |
| returns | string | A unique |
The provided listener is a StatusListener function, and will be called with a reference to the Persister and the new Status: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener method removes a listener that was previously added to the Persister.
delListener(listenerId: string): this| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | this | A reference to the |
Use the Id returned by whichever method was used to add the listener. Note that the Persister may re-use this Id for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
destroy
The destroy method should be called when this Persister object is no longer used.
destroy(): Promise<PglitePersister>| returns | Promise<PglitePersister> | A Promise containing a reference to the |
|---|
This guarantees that all of the listeners that the object registered with the underlying Store and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad method and the stopAutoSave method in succession. This method is asynchronous.
Example
This example creates a Store, associates a Persister object with it (that registers a TablesListener with the underlying Store), and then destroys it again, removing the listener.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
await persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus method lets you find out if the Persister is currently in the process of loading or saving content.
getStatus(): StatusIt can only be doing one or the other (or neither) at any given time. The Status enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister and queries its status.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<PglitePersister>| Type | Description | |
|---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
| returns | Promise<PglitePersister> | A reference to the |
For example, a database Persister may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister.
Example
This example creates a custom Persister object against a newly-created Store and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createStore} from 'tinybase';
import {createCustomPersister} from 'tinybase/persisters';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
() => checkRemoteSystemIsReady(),
() => sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
startAutoPersisting
The startAutoPersist method is a convenience method that starts both automatic loading and saving of the Store data.
startAutoPersisting(
initialContent?: Content | () => Content,
startSaveFirst?: boolean,
): Promise<PglitePersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
startSaveFirst? | boolean | Whether to start saving before loading, defaulting to false. |
| returns | Promise<PglitePersister> | A Promise containing a reference to the |
This simply runs the startAutoLoad and startAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
This method can take initialContent to pass to the startAutoLoad method. See its documentation for more details.
If for some reason you want to start saving before you start loading, pass true as the second parameter.
Example
and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
persister.destroy();
Since
v6.1.0
stopAutoPersisting
The stopAutoPersist method is a convenience method that stops both automatic loading and saving of the Store data.
stopAutoPersisting(stopSaveFirst?: boolean): Promise<PglitePersister>| Type | Description | |
|---|---|---|
stopSaveFirst? | boolean | Whether to stop saving before loading, defaulting to false. |
| returns | Promise<PglitePersister> | A Promise containing a reference to the |
This simply runs the stopAutoLoad and stopAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
If for some reason you want to stop saving before you stop loading, pass true as the second parameter.
Example
automatically loading and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoPersisting();
console.log(persister.isAutoSaving());
// -> false
console.log(persister.isAutoLoading());
// -> false
persister.destroy();
Since
v6.1.0
Load methods
isAutoLoading
The isAutoLoading method lets you find out if the Persister is currently automatically loading its content.
isAutoLoading(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoLoading.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once.
load(initialContent?: Content | () => Content): Promise<PglitePersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<PglitePersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load method is called, data has previously been persisted and instead, that is loaded.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once, and then continuously.
startAutoLoad(initialContent?: Content | () => Content): Promise<PglitePersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<PglitePersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method first runs a single call to the load method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store.
This method is asynchronous because it starts by making a single call to the asynchronous load method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store, and loads data into it from the browser's session storage, which at first is empty (so the initialTables parameter is used). Subsequent changes to the underlying storage are then reflected in the Store (in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad method stops the automatic loading of data from storage previously started with the startAutoLoad method.
stopAutoLoad(): Promise<PglitePersister>| returns | Promise<PglitePersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically load, this method has no effect. This method is asynchronous.
Example
This example creates an empty Store, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
isAutoSaving
The isAutoSaving method lets you find out if the Persister is currently automatically saving its content.
isAutoSaving(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoSaving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save method takes data from the Store with which the Persister is associated and persists it into storage, once.
save(): Promise<PglitePersister>| returns | Promise<PglitePersister> | A Promise containing a reference to the |
|---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save method takes data from the Store with which the Persister is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<PglitePersister>| returns | Promise<PglitePersister> | A Promise containing a reference to the |
|---|
This method first runs a single call to the save method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave method stops the automatic save of data to storage previously started with the startAutoSave method.
stopAutoSave(): Promise<PglitePersister>| returns | Promise<PglitePersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically save, this method has no effect. This method is asynchronous.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
await persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
getStats
The getStats method provides a set of statistics about the Persister, and is used for debugging purposes.
getStats(): PersisterStats| returns | PersisterStats | A |
|---|
The PersisterStats object contains a count of the number of times the Persister has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister object. Remember that the startAutoLoad method invokes an explicit load when it starts, and the startAutoSave method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store and to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
createPglitePersister
The createPglitePersister function creates a PglitePersister object that can persist the Store to a local PGlite database.
createPglitePersister(
store: Store | MergeableStore,
pglite: PGlite,
configOrStoreTableName?: string | DatabasePersisterConfig,
onSqlCommand?: (sql: string, params?: any[]) => void,
onIgnoredError?: (error: any) => void,
): Promise<PglitePersister>| Type | Description | |
|---|---|---|
store | Store | MergeableStore | The |
pglite | PGlite | The database connection that was returned from |
configOrStoreTableName? | string | DatabasePersisterConfig | A |
onSqlCommand? | (sql: string, params?: any[]) => void | An optional handler called every time the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | Promise<PglitePersister> | A reference to the new |
A PglitePersister supports regular Store objects, and can also be used to persist the metadata of a MergeableStore when using the JSON serialization mode, as described below.
As well as providing a reference to the Store to persist, you must provide a pglite parameter which identifies the database connection.
A database Persister uses one of two modes: either a JSON serialization of the whole Store stored in a single row of a table (the default), or a tabular mapping of Table Ids to database table names and vice-versa).
The third argument is a DatabasePersisterConfig object that configures which of those modes to use, and settings for each. If the third argument is simply a string, it is used as the storeTableName property of the JSON serialization.
See the documentation for the DpcJson and DpcTabular types for more information on how both of those modes can be configured.
This method is asynchronous because it will await the creation of dedicated new connections to the database. You will need to await a call to this function or handle the return type natively as a Promise.
Examples
This example creates a PglitePersister object and persists the Store to a local PGlite database as a JSON serialization into the my_tinybase table. It makes a change to the database directly and then reloads it back into the Store.
import {PGlite} from '@electric-sql/pglite';
import {createStore} from 'tinybase';
import {createPglitePersister} from 'tinybase/persisters/persister-pglite';
const pglite = await PGlite.create();
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = await createPglitePersister(
store,
pglite,
'my_tinybase',
);
await persister.save();
// Store will be saved to the database.
console.log((await pglite.query('SELECT * FROM my_tinybase;')).rows);
// -> [{_id: '_', store: '[{"pets":{"fido":{"species":"dog"}}},{}]'}]
await pglite.query(`UPDATE my_tinybase SET store = $1 WHERE _id = '_';`, [
'[{"pets":{"felix":{"species":"cat"}}},{}]',
]);
await persister.load();
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
await persister.destroy();
await pglite.close();
This example creates a PglitePersister object and persists the Store to a local PGlite database with tabular mapping.
import {PGlite} from '@electric-sql/pglite';
import {createStore} from 'tinybase';
import {createPglitePersister} from 'tinybase/persisters/persister-pglite';
const pglite = await PGlite.create();
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = await createPglitePersister(store, pglite, {
mode: 'tabular',
tables: {load: {pets: 'pets'}, save: {pets: 'pets'}},
});
await persister.save();
console.log((await pglite.query('SELECT * FROM pets;')).rows);
// -> [{_id: 'fido', species: '"dog"'}]
// Note that Cells and Values are JSON-encoded in PostgreSQL databases.
await pglite.query(
`INSERT INTO pets (_id, species) VALUES ('felix', '"cat"')`,
);
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
await persister.destroy();
await pglite.query('DROP TABLE IF EXISTS pets');
await pglite.close();
Since
5.2.0
persister-postgres
The persister-postgres module of the TinyBase project lets you save and load Store data to and from a PostgreSQL database (in an appropriate environment).
See also
Database Persistence guide
Since
5.2.0
Interfaces
PostgresPersister
The PostgresPersister interface represents a Persister that lets you save and load Store data to and from a local PostgreSQL database.
You should use the createPostgresPersister function to create a PostgresPersister object.
It is a minor extension to the Persister interface and simply provides an extra getSql method for accessing a reference to the database connection the Store is being persisted to.
Since
5.2.0
Getter methods
getStore
The getStore method returns a reference to the underlying Store or MergeableStore that is backing this Persister object.
getStore(): Store | MergeableStore| returns | Store | MergeableStore | A reference to the |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets its reference in order to update its data.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getSql
The getSql method returns a reference to the database connection the Store is being persisted to.
getSql(): Sql<{}>| returns | Sql<{}> | A reference to the database connection. |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets the database connection back out again.
import postgres from 'postgres';
import {createStore} from 'tinybase';
import {createPostgresPersister} from 'tinybase/persisters/persister-postgres';
const sql = postgres('postgres://localhost:5432/tinybase');
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = await createPostgresPersister(store, sql, 'my_tinybase');
console.log(persister.getSql() == sql);
// -> true
await persister.destroy();
await sql.end();
Since
5.2.0
Listener methods
addStatusListener
The addStatusListener method registers a listener function with the Persister that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<StoreOrMergeableStore>): string| Type | Description | |
|---|---|---|
listener | StatusListener<StoreOrMergeableStore> | The function that will be called whenever the |
| returns | string | A unique |
The provided listener is a StatusListener function, and will be called with a reference to the Persister and the new Status: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener method removes a listener that was previously added to the Persister.
delListener(listenerId: string): this| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | this | A reference to the |
Use the Id returned by whichever method was used to add the listener. Note that the Persister may re-use this Id for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
destroy
The destroy method should be called when this Persister object is no longer used.
destroy(): Promise<PostgresPersister>| returns | Promise<PostgresPersister> | A Promise containing a reference to the |
|---|
This guarantees that all of the listeners that the object registered with the underlying Store and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad method and the stopAutoSave method in succession. This method is asynchronous.
Example
This example creates a Store, associates a Persister object with it (that registers a TablesListener with the underlying Store), and then destroys it again, removing the listener.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
await persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus method lets you find out if the Persister is currently in the process of loading or saving content.
getStatus(): StatusIt can only be doing one or the other (or neither) at any given time. The Status enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister and queries its status.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<PostgresPersister>| Type | Description | |
|---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
| returns | Promise<PostgresPersister> | A reference to the |
For example, a database Persister may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister.
Example
This example creates a custom Persister object against a newly-created Store and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createStore} from 'tinybase';
import {createCustomPersister} from 'tinybase/persisters';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
() => checkRemoteSystemIsReady(),
() => sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
startAutoPersisting
The startAutoPersist method is a convenience method that starts both automatic loading and saving of the Store data.
startAutoPersisting(
initialContent?: Content | () => Content,
startSaveFirst?: boolean,
): Promise<PostgresPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
startSaveFirst? | boolean | Whether to start saving before loading, defaulting to false. |
| returns | Promise<PostgresPersister> | A Promise containing a reference to the |
This simply runs the startAutoLoad and startAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
This method can take initialContent to pass to the startAutoLoad method. See its documentation for more details.
If for some reason you want to start saving before you start loading, pass true as the second parameter.
Example
and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
persister.destroy();
Since
v6.1.0
stopAutoPersisting
The stopAutoPersist method is a convenience method that stops both automatic loading and saving of the Store data.
stopAutoPersisting(stopSaveFirst?: boolean): Promise<PostgresPersister>| Type | Description | |
|---|---|---|
stopSaveFirst? | boolean | Whether to stop saving before loading, defaulting to false. |
| returns | Promise<PostgresPersister> | A Promise containing a reference to the |
This simply runs the stopAutoLoad and stopAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
If for some reason you want to stop saving before you stop loading, pass true as the second parameter.
Example
automatically loading and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoPersisting();
console.log(persister.isAutoSaving());
// -> false
console.log(persister.isAutoLoading());
// -> false
persister.destroy();
Since
v6.1.0
Load methods
isAutoLoading
The isAutoLoading method lets you find out if the Persister is currently automatically loading its content.
isAutoLoading(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoLoading.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once.
load(initialContent?: Content | () => Content): Promise<PostgresPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<PostgresPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load method is called, data has previously been persisted and instead, that is loaded.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once, and then continuously.
startAutoLoad(initialContent?: Content | () => Content): Promise<PostgresPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<PostgresPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method first runs a single call to the load method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store.
This method is asynchronous because it starts by making a single call to the asynchronous load method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store, and loads data into it from the browser's session storage, which at first is empty (so the initialTables parameter is used). Subsequent changes to the underlying storage are then reflected in the Store (in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad method stops the automatic loading of data from storage previously started with the startAutoLoad method.
stopAutoLoad(): Promise<PostgresPersister>| returns | Promise<PostgresPersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically load, this method has no effect. This method is asynchronous.
Example
This example creates an empty Store, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
isAutoSaving
The isAutoSaving method lets you find out if the Persister is currently automatically saving its content.
isAutoSaving(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoSaving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save method takes data from the Store with which the Persister is associated and persists it into storage, once.
save(): Promise<PostgresPersister>| returns | Promise<PostgresPersister> | A Promise containing a reference to the |
|---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save method takes data from the Store with which the Persister is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<PostgresPersister>| returns | Promise<PostgresPersister> | A Promise containing a reference to the |
|---|
This method first runs a single call to the save method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave method stops the automatic save of data to storage previously started with the startAutoSave method.
stopAutoSave(): Promise<PostgresPersister>| returns | Promise<PostgresPersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically save, this method has no effect. This method is asynchronous.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
await persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
getStats
The getStats method provides a set of statistics about the Persister, and is used for debugging purposes.
getStats(): PersisterStats| returns | PersisterStats | A |
|---|
The PersisterStats object contains a count of the number of times the Persister has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister object. Remember that the startAutoLoad method invokes an explicit load when it starts, and the startAutoSave method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store and to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
createPostgresPersister
The createPostgresPersister function creates a PostgresPersister object that can persist the Store to a local PostgreSQL database.
createPostgresPersister(
store: Store | MergeableStore,
sql: Sql<{}>,
configOrStoreTableName?: string | DatabasePersisterConfig,
onSqlCommand?: (sql: string, params?: any[]) => void,
onIgnoredError?: (error: any) => void,
): Promise<PostgresPersister>| Type | Description | |
|---|---|---|
store | Store | MergeableStore | The |
sql | Sql<{}> | The database connection that was returned from |
configOrStoreTableName? | string | DatabasePersisterConfig | A |
onSqlCommand? | (sql: string, params?: any[]) => void | An optional handler called every time the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | Promise<PostgresPersister> | A reference to the new |
A PostgresPersister supports regular Store objects, and can also be used to persist the metadata of a MergeableStore when using the JSON serialization mode, as described below.
As well as providing a reference to the Store to persist, you must provide a sql parameter which identifies the database connection.
A database Persister uses one of two modes: either a JSON serialization of the whole Store stored in a single row of a table (the default), or a tabular mapping of Table Ids to database table names and vice-versa).
The third argument is a DatabasePersisterConfig object that configures which of those modes to use, and settings for each. If the third argument is simply a string, it is used as the storeTableName property of the JSON serialization.
See the documentation for the DpcJson and DpcTabular types for more information on how both of those modes can be configured.
This method is asynchronous because it will await the creation of dedicated new connections to the database. You will need to await a call to this function or handle the return type natively as a Promise.
Examples
This example creates a PostgresPersister object and persists the Store to a local PostgreSQL database as a JSON serialization into the my_tinybase table. It makes a change to the database directly and then reloads it back into the Store.
import postgres from 'postgres';
import {createStore} from 'tinybase';
import {createPostgresPersister} from 'tinybase/persisters/persister-postgres';
const sql = postgres('postgres://localhost:5432/tinybase');
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = await createPostgresPersister(store, sql, 'my_tinybase');
await persister.save();
// Store will be saved to the database.
console.log(await sql`SELECT * FROM my_tinybase;`);
// -> [{_id: '_', store: '[{"pets":{"fido":{"species":"dog"}}},{}]'}]
const json = '[{"pets":{"felix":{"species":"cat"}}},{}]';
await sql`UPDATE my_tinybase SET store = ${json} WHERE _id = '_';`;
await persister.load();
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
await persister.destroy();
await sql.end();
This example creates a PostgresPersister object and persists the Store to a local PostgreSQL database with tabular mapping.
import postgres from 'postgres';
import {createStore} from 'tinybase';
import {createPostgresPersister} from 'tinybase/persisters/persister-postgres';
const sql = postgres('postgres://localhost:5432/tinybase');
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = await createPostgresPersister(store, sql, {
mode: 'tabular',
tables: {load: {pets: 'pets'}, save: {pets: 'pets'}},
});
await persister.save();
console.log(await sql`SELECT * FROM pets;`);
// -> [{_id: 'fido', species: '"dog"'}]
// Note that Cells and Values are JSON-encoded in PostgreSQL databases.
await sql`INSERT INTO pets (_id, species) VALUES ('felix', '"cat"')`;
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
await persister.destroy();
await sql`DROP TABLE IF EXISTS pets`;
await sql.end();
Since
5.2.0
persister-powersync
The persister-powersync module of the TinyBase project lets you save and load Store data to and from a local SQLite database that is automatically synced using the PowerSync service.
See also
Database Persistence guide
Since
v4.8.0
Interfaces
PowerSyncPersister
The PowerSyncPersister interface represents a Persister that lets you save and load Store data to and from a local SQLite database that is automatically synced using the PowerSync service.
You should use the createPowerSyncPersister function to create a PowerSyncPersister object.
It is a minor extension to the Persister interface and simply provides an extra getPowerSync method for accessing a reference to the PowerSync instance the Store is being persisted to.
Since
v4.8.0
Getter methods
getStore
The getStore method returns a reference to the underlying Store or MergeableStore that is backing this Persister object.
getStore(): Store| returns | Store | A reference to the |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets its reference in order to update its data.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getPowerSync
The getPowerSync method returns a reference to the PowerSync instance the Store is being persisted to.
getPowerSync(): AbstractPowerSyncDatabase| returns | AbstractPowerSyncDatabase | A reference to the PowerSync instance. |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets the PowerSync instance back out again.
import {usePowerSync} from '@powersync/react';
import {createStore} from 'tinybase';
import {createPowerSyncPersister} from 'tinybase/persisters/persister-powersync';
const ps = usePowerSync();
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createPowerSyncPersister(store, ps, 'my_tinybase');
console.log(persister.getPowerSync() == ps);
// -> true
await persister.destroy();
Since
v4.8.0
Listener methods
addStatusListener
The addStatusListener method registers a listener function with the Persister that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<StoreOnly>): string| Type | Description | |
|---|---|---|
listener | StatusListener<StoreOnly> | The function that will be called whenever the |
| returns | string | A unique |
The provided listener is a StatusListener function, and will be called with a reference to the Persister and the new Status: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener method removes a listener that was previously added to the Persister.
delListener(listenerId: string): this| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | this | A reference to the |
Use the Id returned by whichever method was used to add the listener. Note that the Persister may re-use this Id for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
destroy
The destroy method should be called when this Persister object is no longer used.
destroy(): Promise<PowerSyncPersister>| returns | Promise<PowerSyncPersister> | A Promise containing a reference to the |
|---|
This guarantees that all of the listeners that the object registered with the underlying Store and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad method and the stopAutoSave method in succession. This method is asynchronous.
Example
This example creates a Store, associates a Persister object with it (that registers a TablesListener with the underlying Store), and then destroys it again, removing the listener.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
await persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus method lets you find out if the Persister is currently in the process of loading or saving content.
getStatus(): StatusIt can only be doing one or the other (or neither) at any given time. The Status enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister and queries its status.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<PowerSyncPersister>| Type | Description | |
|---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
| returns | Promise<PowerSyncPersister> | A reference to the |
For example, a database Persister may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister.
Example
This example creates a custom Persister object against a newly-created Store and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createStore} from 'tinybase';
import {createCustomPersister} from 'tinybase/persisters';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
() => checkRemoteSystemIsReady(),
() => sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
startAutoPersisting
The startAutoPersist method is a convenience method that starts both automatic loading and saving of the Store data.
startAutoPersisting(
initialContent?: Content | () => Content,
startSaveFirst?: boolean,
): Promise<PowerSyncPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
startSaveFirst? | boolean | Whether to start saving before loading, defaulting to false. |
| returns | Promise<PowerSyncPersister> | A Promise containing a reference to the |
This simply runs the startAutoLoad and startAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
This method can take initialContent to pass to the startAutoLoad method. See its documentation for more details.
If for some reason you want to start saving before you start loading, pass true as the second parameter.
Example
and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
persister.destroy();
Since
v6.1.0
stopAutoPersisting
The stopAutoPersist method is a convenience method that stops both automatic loading and saving of the Store data.
stopAutoPersisting(stopSaveFirst?: boolean): Promise<PowerSyncPersister>| Type | Description | |
|---|---|---|
stopSaveFirst? | boolean | Whether to stop saving before loading, defaulting to false. |
| returns | Promise<PowerSyncPersister> | A Promise containing a reference to the |
This simply runs the stopAutoLoad and stopAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
If for some reason you want to stop saving before you stop loading, pass true as the second parameter.
Example
automatically loading and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoPersisting();
console.log(persister.isAutoSaving());
// -> false
console.log(persister.isAutoLoading());
// -> false
persister.destroy();
Since
v6.1.0
Load methods
isAutoLoading
The isAutoLoading method lets you find out if the Persister is currently automatically loading its content.
isAutoLoading(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoLoading.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once.
load(initialContent?: Content | () => Content): Promise<PowerSyncPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<PowerSyncPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load method is called, data has previously been persisted and instead, that is loaded.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once, and then continuously.
startAutoLoad(initialContent?: Content | () => Content): Promise<PowerSyncPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<PowerSyncPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method first runs a single call to the load method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store.
This method is asynchronous because it starts by making a single call to the asynchronous load method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store, and loads data into it from the browser's session storage, which at first is empty (so the initialTables parameter is used). Subsequent changes to the underlying storage are then reflected in the Store (in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad method stops the automatic loading of data from storage previously started with the startAutoLoad method.
stopAutoLoad(): Promise<PowerSyncPersister>| returns | Promise<PowerSyncPersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically load, this method has no effect. This method is asynchronous.
Example
This example creates an empty Store, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
isAutoSaving
The isAutoSaving method lets you find out if the Persister is currently automatically saving its content.
isAutoSaving(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoSaving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save method takes data from the Store with which the Persister is associated and persists it into storage, once.
save(): Promise<PowerSyncPersister>| returns | Promise<PowerSyncPersister> | A Promise containing a reference to the |
|---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save method takes data from the Store with which the Persister is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<PowerSyncPersister>| returns | Promise<PowerSyncPersister> | A Promise containing a reference to the |
|---|
This method first runs a single call to the save method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave method stops the automatic save of data to storage previously started with the startAutoSave method.
stopAutoSave(): Promise<PowerSyncPersister>| returns | Promise<PowerSyncPersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically save, this method has no effect. This method is asynchronous.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
await persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
getStats
The getStats method provides a set of statistics about the Persister, and is used for debugging purposes.
getStats(): PersisterStats| returns | PersisterStats | A |
|---|
The PersisterStats object contains a count of the number of times the Persister has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister object. Remember that the startAutoLoad method invokes an explicit load when it starts, and the startAutoSave method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store and to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
createPowerSyncPersister
The createPowerSyncPersister function creates a PowerSyncPersister object that can persist the Store to a local SQLite database that is automatically synced using the PowerSync service.
createPowerSyncPersister(
store: Store,
powerSync: AbstractPowerSyncDatabase,
configOrStoreTableName?: string | DatabasePersisterConfig,
onSqlCommand?: (sql: string, params?: any[]) => void,
onIgnoredError?: (error: any) => void,
): PowerSyncPersister| Type | Description | |
|---|---|---|
store | Store | The |
powerSync | AbstractPowerSyncDatabase | The PowerSync instance. |
configOrStoreTableName? | string | DatabasePersisterConfig | A |
onSqlCommand? | (sql: string, params?: any[]) => void | An optional handler called every time the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | PowerSyncPersister | A reference to the new |
A PowerSyncPersister only supports regular Store objects, and cannot be used to persist the metadata of a MergeableStore.
As well as providing a reference to the Store to persist, you must provide a powerSync parameter which identifies the PowerSync instance.
A database Persister uses one of two modes: either a JSON serialization of the whole Store stored in a single row of a table (the default), or a tabular mapping of Table Ids to database table names and vice-versa).
The third argument is a DatabasePersisterConfig object that configures which of those modes to use, and settings for each. If the third argument is simply a string, it is used as the storeTableName property of the JSON serialization.
See the documentation for the DpcJson and DpcTabular types for more information on how both of those modes can be configured.
Examples
This example creates a PowerSyncPersister object and persists the Store to a local PowerSync instance. It makes a change to the database directly and then reloads it back into the Store.
import {usePowerSync} from '@powersync/react';
import {createStore} from 'tinybase';
import {createPowerSyncPersister} from 'tinybase/persisters/persister-powersync';
const ps = usePowerSync();
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createPowerSyncPersister(store, ps, 'my_tinybase');
await persister.save();
// Store will be saved to the database.
console.log(
await new Promise((resolve) =>
ps.execute('SELECT * FROM my_tinybase;', (_, rows) => resolve(rows)),
),
);
// -> [{_id: '_', store: '[{"pets":{"fido":{"species":"dog"}}},{}]'}]
await new Promise((resolve) =>
ps.execute(
'UPDATE my_tinybase SET store = ' +
`'[{"pets":{"felix":{"species":"cat"}}},{}]' WHERE _id = '_';`,
resolve,
),
);
await persister.load();
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
await persister.destroy();
This example creates a PowerSyncPersister object and persists the Store to a local PowerSync instance with tabular mapping.
import {usePowerSync} from '@powersync/react';
import {createStore} from 'tinybase';
import {createPowerSyncPersister} from 'tinybase/persisters/persister-powersync';
const ps = usePowerSync();
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createPowerSyncPersister(store, ps, {
mode: 'tabular',
tables: {load: {pets: 'pets'}, save: {pets: 'pets'}},
});
await persister.save();
console.log(
await new Promise((resolve) =>
ps.execute('SELECT * FROM pets;', (_, rows) => resolve(rows)),
),
);
// -> [{_id: 'fido', species: 'dog'}]
await new Promise((resolve) =>
ps.execute(
`INSERT INTO pets (_id, species) VALUES ('felix', 'cat')`,
resolve,
),
);
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
await persister.destroy();
Since
v4.8.0
persister-react-native-mmkv
The persister-react-native-mmkv module of the TinyBase project lets you save and load Store data to and from a MMKV storage using the react-native-mmkv module (in an appropriate React Native environment).
See also
Database Persistence guide
Since
v6.5.0
Interfaces
ReactNativeMmkvPersister
The ReactNativeMmkvPersister interface represents a Persister that lets you save and load Store data to and from a react-native-mmkv storage.
You should use the createReactNativeMmkvPersister function to create a ReactNativeMmkvPersister object.
It is a minor extension to the Persister interface and simply provides an extra getStorageName method for accessing the unique key of the storage location the Store is being persisted to.
Since
v6.5.0
Getter methods
getStore
The getStore method returns a reference to the underlying Store or MergeableStore that is backing this Persister object.
getStore(): Store | MergeableStore| returns | Store | MergeableStore | A reference to the |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets its reference in order to update its data.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getStorageName
The getStorageName method returns the unique key of the storage location the Store is being persisted to.
getStorageName(): string| returns | string | The unique key of the storage location. |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets the unique key of the storage location back out again.
import {MMKV} from 'react-native-mmkv';
import {createStore} from 'tinybase';
import {createReactNativeMmkvPersister} from 'tinybase/persisters/persister-react-native-mmkv';
const storage = new MMKV();
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createReactNativeMmkvPersister(
store,
storage,
'my_tinybase',
);
console.log(persister.getStorageName() == 'my_tinybase');
// -> true
await persister.destroy();
Since
v6.5.0
Listener methods
addStatusListener
The addStatusListener method registers a listener function with the Persister that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<StoreOrMergeableStore>): string| Type | Description | |
|---|---|---|
listener | StatusListener<StoreOrMergeableStore> | The function that will be called whenever the |
| returns | string | A unique |
The provided listener is a StatusListener function, and will be called with a reference to the Persister and the new Status: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener method removes a listener that was previously added to the Persister.
delListener(listenerId: string): this| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | this | A reference to the |
Use the Id returned by whichever method was used to add the listener. Note that the Persister may re-use this Id for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
destroy
The destroy method should be called when this Persister object is no longer used.
destroy(): Promise<ReactNativeMmkvPersister>| returns | Promise<ReactNativeMmkvPersister> | A Promise containing a reference to the |
|---|
This guarantees that all of the listeners that the object registered with the underlying Store and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad method and the stopAutoSave method in succession. This method is asynchronous.
Example
This example creates a Store, associates a Persister object with it (that registers a TablesListener with the underlying Store), and then destroys it again, removing the listener.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
await persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus method lets you find out if the Persister is currently in the process of loading or saving content.
getStatus(): StatusIt can only be doing one or the other (or neither) at any given time. The Status enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister and queries its status.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<ReactNativeMmkvPersister>| Type | Description | |
|---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
| returns | Promise<ReactNativeMmkvPersister> | A reference to the |
For example, a database Persister may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister.
Example
This example creates a custom Persister object against a newly-created Store and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createStore} from 'tinybase';
import {createCustomPersister} from 'tinybase/persisters';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
() => checkRemoteSystemIsReady(),
() => sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
startAutoPersisting
The startAutoPersist method is a convenience method that starts both automatic loading and saving of the Store data.
startAutoPersisting(
initialContent?: Content | () => Content,
startSaveFirst?: boolean,
): Promise<ReactNativeMmkvPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
startSaveFirst? | boolean | Whether to start saving before loading, defaulting to false. |
| returns | Promise<ReactNativeMmkvPersister> | A Promise containing a reference to the |
This simply runs the startAutoLoad and startAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
This method can take initialContent to pass to the startAutoLoad method. See its documentation for more details.
If for some reason you want to start saving before you start loading, pass true as the second parameter.
Example
and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
persister.destroy();
Since
v6.1.0
stopAutoPersisting
The stopAutoPersist method is a convenience method that stops both automatic loading and saving of the Store data.
stopAutoPersisting(stopSaveFirst?: boolean): Promise<ReactNativeMmkvPersister>| Type | Description | |
|---|---|---|
stopSaveFirst? | boolean | Whether to stop saving before loading, defaulting to false. |
| returns | Promise<ReactNativeMmkvPersister> | A Promise containing a reference to the |
This simply runs the stopAutoLoad and stopAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
If for some reason you want to stop saving before you stop loading, pass true as the second parameter.
Example
automatically loading and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoPersisting();
console.log(persister.isAutoSaving());
// -> false
console.log(persister.isAutoLoading());
// -> false
persister.destroy();
Since
v6.1.0
Load methods
isAutoLoading
The isAutoLoading method lets you find out if the Persister is currently automatically loading its content.
isAutoLoading(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoLoading.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once.
load(initialContent?: Content | () => Content): Promise<ReactNativeMmkvPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<ReactNativeMmkvPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load method is called, data has previously been persisted and instead, that is loaded.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once, and then continuously.
startAutoLoad(initialContent?: Content | () => Content): Promise<ReactNativeMmkvPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<ReactNativeMmkvPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method first runs a single call to the load method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store.
This method is asynchronous because it starts by making a single call to the asynchronous load method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store, and loads data into it from the browser's session storage, which at first is empty (so the initialTables parameter is used). Subsequent changes to the underlying storage are then reflected in the Store (in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad method stops the automatic loading of data from storage previously started with the startAutoLoad method.
stopAutoLoad(): Promise<ReactNativeMmkvPersister>| returns | Promise<ReactNativeMmkvPersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically load, this method has no effect. This method is asynchronous.
Example
This example creates an empty Store, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
isAutoSaving
The isAutoSaving method lets you find out if the Persister is currently automatically saving its content.
isAutoSaving(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoSaving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save method takes data from the Store with which the Persister is associated and persists it into storage, once.
save(): Promise<ReactNativeMmkvPersister>| returns | Promise<ReactNativeMmkvPersister> | A Promise containing a reference to the |
|---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save method takes data from the Store with which the Persister is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<ReactNativeMmkvPersister>| returns | Promise<ReactNativeMmkvPersister> | A Promise containing a reference to the |
|---|
This method first runs a single call to the save method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave method stops the automatic save of data to storage previously started with the startAutoSave method.
stopAutoSave(): Promise<ReactNativeMmkvPersister>| returns | Promise<ReactNativeMmkvPersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically save, this method has no effect. This method is asynchronous.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
await persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
getStats
The getStats method provides a set of statistics about the Persister, and is used for debugging purposes.
getStats(): PersisterStats| returns | PersisterStats | A |
|---|
The PersisterStats object contains a count of the number of times the Persister has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister object. Remember that the startAutoLoad method invokes an explicit load when it starts, and the startAutoSave method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store and to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
createReactNativeMmkvPersister
The createReactNativeMmkvPersister function creates a ReactNativeMmkvPersister object that can persist the Store to a local react-native-mmkv storage.
createReactNativeMmkvPersister(
store: Store | MergeableStore,
storage: MMKV,
storageName?: string,
onIgnoredError?: (error: any) => void,
): ReactNativeMmkvPersister| Type | Description | |
|---|---|---|
store | Store | MergeableStore | The |
storage | MMKV | The MMKV storage instance. |
storageName? | string | The unique key to identify the storage location. |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | ReactNativeMmkvPersister | A reference to the new |
A ReactNativeMmkvPersister supports both regular Store and MergeableStore objects.
As well as providing a reference to the Store to persist, you must provide a storage parameter which identifies the MMKV storage instance.
The third argument is a storageName string that configures which key to use for the storage key.
Example
This example creates a ReactNativeMmkvPersister object and persists the Store to a MMKV storage instance as a JSON serialization into the my_tinybase key. It makes a change to the storage directly and then reloads it back into the Store.
import {MMKV} from 'react-native-mmkv';
import {createStore} from 'tinybase';
import {createReactNativeMmkvPersister} from 'tinybase/persisters/persister-react-native-mmkv';
const storage = new MMKV();
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createReactNativeMmkvPersister(
store,
storage,
'my_tinybase',
);
await persister.save();
// Store will be saved to the MMKV storage.
console.log(storage.getString('my_tinybase'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
await persister.destroy();
storage.delete('my_tinybase');
Since
v6.5.0
persister-react-native-sqlite
The persister-react-native-sqlite module of the TinyBase project lets you save and load Store data to and from a SQLite database using the react-native-sqlite-storage module (in an appropriate React Native environment).
See also
Database Persistence guide
Since
v6.4.0
Interfaces
ReactNativeSqlitePersister
The ReactNativeSqlitePersister interface represents a Persister that lets you save and load Store data to and from a react-native-sqlite-storage database.
You should use the createReactNativeSqlitePersister function to create an ReactNativeSqlitePersister object.
It is a minor extension to the Persister interface and simply provides an extra getDb method for accessing a reference to the database instance the Store is being persisted to.
Since
v6.4.0
Getter methods
getStore
The getStore method returns a reference to the underlying Store or MergeableStore that is backing this Persister object.
getStore(): Store | MergeableStore| returns | Store | MergeableStore | A reference to the |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets its reference in order to update its data.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getDb
The getDb method returns a reference to the database instance the Store is being persisted to.
getDb(): SQLiteDatabase| returns | SQLiteDatabase | A reference to the database instance. |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets the database instance back out again.
import {enablePromise, openDatabase} from 'react-native-sqlite-storage';
import {createStore} from 'tinybase';
import {createReactNativeSqlitePersister} from 'tinybase/persisters/persister-react-native-sqlite';
enablePromise(true);
const db = await openDatabase({name: 'my.db', location: 'default'});
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createReactNativeSqlitePersister(
store,
db,
'my_tinybase',
);
console.log(persister.getDb() == db);
// -> true
await persister.destroy();
Since
v6.4.0
Listener methods
addStatusListener
The addStatusListener method registers a listener function with the Persister that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<StoreOrMergeableStore>): string| Type | Description | |
|---|---|---|
listener | StatusListener<StoreOrMergeableStore> | The function that will be called whenever the |
| returns | string | A unique |
The provided listener is a StatusListener function, and will be called with a reference to the Persister and the new Status: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener method removes a listener that was previously added to the Persister.
delListener(listenerId: string): this| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | this | A reference to the |
Use the Id returned by whichever method was used to add the listener. Note that the Persister may re-use this Id for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
destroy
The destroy method should be called when this Persister object is no longer used.
destroy(): Promise<ReactNativeSqlitePersister>| returns | Promise<ReactNativeSqlitePersister> | A Promise containing a reference to the |
|---|
This guarantees that all of the listeners that the object registered with the underlying Store and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad method and the stopAutoSave method in succession. This method is asynchronous.
Example
This example creates a Store, associates a Persister object with it (that registers a TablesListener with the underlying Store), and then destroys it again, removing the listener.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
await persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus method lets you find out if the Persister is currently in the process of loading or saving content.
getStatus(): StatusIt can only be doing one or the other (or neither) at any given time. The Status enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister and queries its status.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<ReactNativeSqlitePersister>| Type | Description | |
|---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
| returns | Promise<ReactNativeSqlitePersister> | A reference to the |
For example, a database Persister may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister.
Example
This example creates a custom Persister object against a newly-created Store and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createStore} from 'tinybase';
import {createCustomPersister} from 'tinybase/persisters';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
() => checkRemoteSystemIsReady(),
() => sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
startAutoPersisting
The startAutoPersist method is a convenience method that starts both automatic loading and saving of the Store data.
startAutoPersisting(
initialContent?: Content | () => Content,
startSaveFirst?: boolean,
): Promise<ReactNativeSqlitePersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
startSaveFirst? | boolean | Whether to start saving before loading, defaulting to false. |
| returns | Promise<ReactNativeSqlitePersister> | A Promise containing a reference to the |
This simply runs the startAutoLoad and startAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
This method can take initialContent to pass to the startAutoLoad method. See its documentation for more details.
If for some reason you want to start saving before you start loading, pass true as the second parameter.
Example
and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
persister.destroy();
Since
v6.1.0
stopAutoPersisting
The stopAutoPersist method is a convenience method that stops both automatic loading and saving of the Store data.
stopAutoPersisting(stopSaveFirst?: boolean): Promise<ReactNativeSqlitePersister>| Type | Description | |
|---|---|---|
stopSaveFirst? | boolean | Whether to stop saving before loading, defaulting to false. |
| returns | Promise<ReactNativeSqlitePersister> | A Promise containing a reference to the |
This simply runs the stopAutoLoad and stopAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
If for some reason you want to stop saving before you stop loading, pass true as the second parameter.
Example
automatically loading and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoPersisting();
console.log(persister.isAutoSaving());
// -> false
console.log(persister.isAutoLoading());
// -> false
persister.destroy();
Since
v6.1.0
Load methods
isAutoLoading
The isAutoLoading method lets you find out if the Persister is currently automatically loading its content.
isAutoLoading(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoLoading.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once.
load(initialContent?: Content | () => Content): Promise<ReactNativeSqlitePersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<ReactNativeSqlitePersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load method is called, data has previously been persisted and instead, that is loaded.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once, and then continuously.
startAutoLoad(initialContent?: Content | () => Content): Promise<ReactNativeSqlitePersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<ReactNativeSqlitePersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method first runs a single call to the load method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store.
This method is asynchronous because it starts by making a single call to the asynchronous load method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store, and loads data into it from the browser's session storage, which at first is empty (so the initialTables parameter is used). Subsequent changes to the underlying storage are then reflected in the Store (in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad method stops the automatic loading of data from storage previously started with the startAutoLoad method.
stopAutoLoad(): Promise<ReactNativeSqlitePersister>| returns | Promise<ReactNativeSqlitePersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically load, this method has no effect. This method is asynchronous.
Example
This example creates an empty Store, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
isAutoSaving
The isAutoSaving method lets you find out if the Persister is currently automatically saving its content.
isAutoSaving(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoSaving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save method takes data from the Store with which the Persister is associated and persists it into storage, once.
save(): Promise<ReactNativeSqlitePersister>| returns | Promise<ReactNativeSqlitePersister> | A Promise containing a reference to the |
|---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save method takes data from the Store with which the Persister is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<ReactNativeSqlitePersister>| returns | Promise<ReactNativeSqlitePersister> | A Promise containing a reference to the |
|---|
This method first runs a single call to the save method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave method stops the automatic save of data to storage previously started with the startAutoSave method.
stopAutoSave(): Promise<ReactNativeSqlitePersister>| returns | Promise<ReactNativeSqlitePersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically save, this method has no effect. This method is asynchronous.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
await persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
getStats
The getStats method provides a set of statistics about the Persister, and is used for debugging purposes.
getStats(): PersisterStats| returns | PersisterStats | A |
|---|
The PersisterStats object contains a count of the number of times the Persister has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister object. Remember that the startAutoLoad method invokes an explicit load when it starts, and the startAutoSave method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store and to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
createReactNativeSqlitePersister
The createReactNativeSqlitePersister function creates an ReactNativeSqlitePersister object that can persist the Store to a local react-native-sqlite-storage database.
createReactNativeSqlitePersister(
store: Store | MergeableStore,
db: SQLiteDatabase,
configOrStoreTableName?: string | DatabasePersisterConfig,
onSqlCommand?: (sql: string, params?: any[]) => void,
onIgnoredError?: (error: any) => void,
): ReactNativeSqlitePersister| Type | Description | |
|---|---|---|
store | Store | MergeableStore | The |
db | SQLiteDatabase | The database instance that was returned from |
configOrStoreTableName? | string | DatabasePersisterConfig | A |
onSqlCommand? | (sql: string, params?: any[]) => void | An optional handler called every time the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | ReactNativeSqlitePersister | A reference to the new |
An ReactNativeSqlitePersister supports regular Store objects, and can also be used to persist the metadata of a MergeableStore when using the JSON serialization mode, as described below.
As well as providing a reference to the Store to persist, you must provide a db parameter which identifies the database instance.
Important note: the react-native-sqlite-storage module must have had promises enabled before the database instance is passed in. Use SQLite.enablePromise(true) before initializing the Persister.
A database Persister uses one of two modes: either a JSON serialization of the whole Store stored in a single row of a table (the default), or a tabular mapping of Table Ids to database table names and vice-versa).
The third argument is a DatabasePersisterConfig object that configures which of those modes to use, and settings for each. If the third argument is simply a string, it is used as the storeTableName property of the JSON serialization.
See the documentation for the DpcJson and DpcTabular types for more information on how both of those modes can be configured.
Examples
This example creates a ReactNativeSqlitePersister object and persists the Store to a local SQLite database as a JSON serialization into the my_tinybase table. It makes a change to the database directly and then reloads it back into the Store.
import {enablePromise, openDatabase} from 'react-native-sqlite-storage';
import {createStore} from 'tinybase';
import {createReactNativeSqlitePersister} from 'tinybase/persisters/persister-react-native-sqlite';
enablePromise(true);
const db = await openDatabase({name: 'my.db', location: 'default'});
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createReactNativeSqlitePersister(
store,
db,
'my_tinybase',
);
await persister.save();
// Store will be saved to the database.
console.log(
(await db.executeSql('SELECT * FROM my_tinybase;'))[0].rows.raw(),
);
// -> [{_id: '_', store: '[{"pets":{"fido":{"species":"dog"}}},{}]'}]
await db.executeSql(
'UPDATE my_tinybase SET store = ' +
`'[{"pets":{"felix":{"species":"cat"}}},{}]' WHERE _id = '_';`,
);
await persister.load();
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
await persister.destroy();
This example creates a ReactNativeSqlitePersister object and persists the Store to a local SQLite database with tabular mapping.
import {enablePromise, openDatabase} from 'react-native-sqlite-storage';
import {createStore} from 'tinybase';
import {createReactNativeSqlitePersister} from 'tinybase/persisters/persister-react-native-sqlite';
enablePromise(true);
const db = await openDatabase({name: 'my.db', location: 'default'});
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createReactNativeSqlitePersister(store, db, {
mode: 'tabular',
tables: {load: {pets: 'pets'}, save: {pets: 'pets'}},
});
await persister.save();
console.log((await db.executeSql('SELECT * FROM pets;'))[0].rows.raw());
// -> [{_id: 'fido', species: 'dog'}]
await db.executeSql(
`INSERT INTO pets (_id, species) VALUES ('felix', 'cat')`,
);
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
await persister.destroy();
Since
v6.4.0
persister-remote
The persister-remote module of the TinyBase project lets you save and load Store data to and from a remote server.
See also
Persistence guides
Since
v1.0.0
Interfaces
RemotePersister
The RemotePersister interface represents a Persister that lets you save and load Store data to and from a remote server.
You should use the createRemotePersister function to create a RemotePersister object.
It is a minor extension to the Persister interface and simply provides an extra getUrls method for accessing the URLs the Store is being persisted to.
Since
v4.3.14
Getter methods
getStore
The getStore method returns a reference to the underlying Store or MergeableStore that is backing this Persister object.
getStore(): Store| returns | Store | A reference to the |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets its reference in order to update its data.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getUrls
The getUrls method returns the URLs the Store is being persisted to.
getUrls(): [string, string]| returns | [string, string] | The load and save URLs as a two-item array. |
|---|
Example
This example creates a RemotePersister object against a newly-created Store and then gets the URLs back out again.
import {createStore} from 'tinybase';
import {createRemotePersister} from 'tinybase/persisters/persister-remote';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createRemotePersister(
store,
'https://example.com/load',
'https://example.com/save',
5,
);
console.log(persister.getUrls());
// -> ['https://example.com/load', 'https://example.com/save']
await persister.destroy();
Since
v4.3.14
Listener methods
addStatusListener
The addStatusListener method registers a listener function with the Persister that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<StoreOnly>): string| Type | Description | |
|---|---|---|
listener | StatusListener<StoreOnly> | The function that will be called whenever the |
| returns | string | A unique |
The provided listener is a StatusListener function, and will be called with a reference to the Persister and the new Status: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener method removes a listener that was previously added to the Persister.
delListener(listenerId: string): this| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | this | A reference to the |
Use the Id returned by whichever method was used to add the listener. Note that the Persister may re-use this Id for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
destroy
The destroy method should be called when this Persister object is no longer used.
destroy(): Promise<RemotePersister>| returns | Promise<RemotePersister> | A Promise containing a reference to the |
|---|
This guarantees that all of the listeners that the object registered with the underlying Store and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad method and the stopAutoSave method in succession. This method is asynchronous.
Example
This example creates a Store, associates a Persister object with it (that registers a TablesListener with the underlying Store), and then destroys it again, removing the listener.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
await persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus method lets you find out if the Persister is currently in the process of loading or saving content.
getStatus(): StatusIt can only be doing one or the other (or neither) at any given time. The Status enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister and queries its status.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<RemotePersister>| Type | Description | |
|---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
| returns | Promise<RemotePersister> | A reference to the |
For example, a database Persister may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister.
Example
This example creates a custom Persister object against a newly-created Store and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createStore} from 'tinybase';
import {createCustomPersister} from 'tinybase/persisters';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
() => checkRemoteSystemIsReady(),
() => sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
startAutoPersisting
The startAutoPersist method is a convenience method that starts both automatic loading and saving of the Store data.
startAutoPersisting(
initialContent?: Content | () => Content,
startSaveFirst?: boolean,
): Promise<RemotePersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
startSaveFirst? | boolean | Whether to start saving before loading, defaulting to false. |
| returns | Promise<RemotePersister> | A Promise containing a reference to the |
This simply runs the startAutoLoad and startAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
This method can take initialContent to pass to the startAutoLoad method. See its documentation for more details.
If for some reason you want to start saving before you start loading, pass true as the second parameter.
Example
and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
persister.destroy();
Since
v6.1.0
stopAutoPersisting
The stopAutoPersist method is a convenience method that stops both automatic loading and saving of the Store data.
stopAutoPersisting(stopSaveFirst?: boolean): Promise<RemotePersister>| Type | Description | |
|---|---|---|
stopSaveFirst? | boolean | Whether to stop saving before loading, defaulting to false. |
| returns | Promise<RemotePersister> | A Promise containing a reference to the |
This simply runs the stopAutoLoad and stopAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
If for some reason you want to stop saving before you stop loading, pass true as the second parameter.
Example
automatically loading and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoPersisting();
console.log(persister.isAutoSaving());
// -> false
console.log(persister.isAutoLoading());
// -> false
persister.destroy();
Since
v6.1.0
Load methods
isAutoLoading
The isAutoLoading method lets you find out if the Persister is currently automatically loading its content.
isAutoLoading(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoLoading.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once.
load(initialContent?: Content | () => Content): Promise<RemotePersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<RemotePersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load method is called, data has previously been persisted and instead, that is loaded.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once, and then continuously.
startAutoLoad(initialContent?: Content | () => Content): Promise<RemotePersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<RemotePersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method first runs a single call to the load method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store.
This method is asynchronous because it starts by making a single call to the asynchronous load method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store, and loads data into it from the browser's session storage, which at first is empty (so the initialTables parameter is used). Subsequent changes to the underlying storage are then reflected in the Store (in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad method stops the automatic loading of data from storage previously started with the startAutoLoad method.
stopAutoLoad(): Promise<RemotePersister>| returns | Promise<RemotePersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically load, this method has no effect. This method is asynchronous.
Example
This example creates an empty Store, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
isAutoSaving
The isAutoSaving method lets you find out if the Persister is currently automatically saving its content.
isAutoSaving(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoSaving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save method takes data from the Store with which the Persister is associated and persists it into storage, once.
save(): Promise<RemotePersister>| returns | Promise<RemotePersister> | A Promise containing a reference to the |
|---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save method takes data from the Store with which the Persister is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<RemotePersister>| returns | Promise<RemotePersister> | A Promise containing a reference to the |
|---|
This method first runs a single call to the save method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave method stops the automatic save of data to storage previously started with the startAutoSave method.
stopAutoSave(): Promise<RemotePersister>| returns | Promise<RemotePersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically save, this method has no effect. This method is asynchronous.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
await persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
getStats
The getStats method provides a set of statistics about the Persister, and is used for debugging purposes.
getStats(): PersisterStats| returns | PersisterStats | A |
|---|
The PersisterStats object contains a count of the number of times the Persister has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister object. Remember that the startAutoLoad method invokes an explicit load when it starts, and the startAutoSave method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store and to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
createRemotePersister
The createRemotePersister function creates a RemotePersister object that can persist the Store to a remote server.
createRemotePersister(
store: Store,
loadUrl: string,
saveUrl: string,
autoLoadIntervalSeconds?: number,
onIgnoredError?: (error: any) => void,
): RemotePersister| Type | Description | |
|---|---|---|
store | Store | The |
loadUrl | string | The endpoint that supports a |
saveUrl | string | The endpoint that supports a |
autoLoadIntervalSeconds? | number | How often to poll the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | RemotePersister | A reference to the new |
A RemotePersister only supports regular Store objects, and cannot be used to persist the metadata of a MergeableStore.
As well as providing a reference to the Store to persist, you must provide loadUrl and saveUrl parameters. These identify the endpoints of the server that support the GET method (to fetch the Store JSON to load) and the POST method (to send the Store JSON to save) respectively.
For when you choose to enable automatic loading for the Persister (with the startAutoLoad method), it will poll the loadUrl for changes, using the ETag HTTP header to identify if things have changed. The autoLoadIntervalSeconds method is used to indicate how often to do this.
If you are implementing the server portion of this functionality yourself, remember to ensure that the ETag header changes every time the server's Store content does - otherwise changes will not be detected by the client.
Example
This example creates a RemotePersister object and persists the Store to a remote server.
import {createStore} from 'tinybase';
import {createRemotePersister} from 'tinybase/persisters/persister-remote';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createRemotePersister(
store,
'https://example.com/load',
'https://example.com/save',
5,
);
await persister.save();
// Store JSON will be sent to server in a POST request.
await persister.load();
// Store JSON will be fetched from server with a GET request.
await persister.destroy();
Since
v1.0.0
persister-sqlite-bun
The persister-sqlite-bun module of the TinyBase project lets you save and load Store data to and from a local Bun SQLite database (in an appropriate environment).
See also
Database Persistence guide
Since
v6.1.0
Interfaces
SqliteBunPersister
The SqliteBunPersister interface represents a Persister that lets you save and load Store data to and from a local Bun SQLite database.
You should use the createSqliteBunPersister function to create a SqliteBunPersister object.
It is a minor extension to the Persister interface and simply provides an extra getDb method for accessing a reference to the database instance the Store is being persisted to.
Since
v6.1.0
Getter methods
getStore
The getStore method returns a reference to the underlying Store or MergeableStore that is backing this Persister object.
getStore(): Store | MergeableStore| returns | Store | MergeableStore | A reference to the |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets its reference in order to update its data.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getDb
The getDb method returns a reference to the database instance the Store is being persisted to.
getDb(): Database| returns | Database | A reference to the database instance. |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets the database instance back out again.
import {Database} from 'bun:sqlite';
import {createStore} from 'tinybase';
import {createSqliteBunPersister} from 'tinybase/persisters/persister-sqlite-bun';
const db = new Database(':memory:');
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSqliteBunPersister(store, db, 'my_tinybase');
console.log(persister.getDb() == db);
// -> true
await persister.destroy();
Since
v6.1.0
Listener methods
addStatusListener
The addStatusListener method registers a listener function with the Persister that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<StoreOrMergeableStore>): string| Type | Description | |
|---|---|---|
listener | StatusListener<StoreOrMergeableStore> | The function that will be called whenever the |
| returns | string | A unique |
The provided listener is a StatusListener function, and will be called with a reference to the Persister and the new Status: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener method removes a listener that was previously added to the Persister.
delListener(listenerId: string): this| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | this | A reference to the |
Use the Id returned by whichever method was used to add the listener. Note that the Persister may re-use this Id for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
destroy
The destroy method should be called when this Persister object is no longer used.
destroy(): Promise<SqliteBunPersister>| returns | Promise<SqliteBunPersister> | A Promise containing a reference to the |
|---|
This guarantees that all of the listeners that the object registered with the underlying Store and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad method and the stopAutoSave method in succession. This method is asynchronous.
Example
This example creates a Store, associates a Persister object with it (that registers a TablesListener with the underlying Store), and then destroys it again, removing the listener.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
await persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus method lets you find out if the Persister is currently in the process of loading or saving content.
getStatus(): StatusIt can only be doing one or the other (or neither) at any given time. The Status enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister and queries its status.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<SqliteBunPersister>| Type | Description | |
|---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
| returns | Promise<SqliteBunPersister> | A reference to the |
For example, a database Persister may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister.
Example
This example creates a custom Persister object against a newly-created Store and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createStore} from 'tinybase';
import {createCustomPersister} from 'tinybase/persisters';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
() => checkRemoteSystemIsReady(),
() => sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
startAutoPersisting
The startAutoPersist method is a convenience method that starts both automatic loading and saving of the Store data.
startAutoPersisting(
initialContent?: Content | () => Content,
startSaveFirst?: boolean,
): Promise<SqliteBunPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
startSaveFirst? | boolean | Whether to start saving before loading, defaulting to false. |
| returns | Promise<SqliteBunPersister> | A Promise containing a reference to the |
This simply runs the startAutoLoad and startAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
This method can take initialContent to pass to the startAutoLoad method. See its documentation for more details.
If for some reason you want to start saving before you start loading, pass true as the second parameter.
Example
and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
persister.destroy();
Since
v6.1.0
stopAutoPersisting
The stopAutoPersist method is a convenience method that stops both automatic loading and saving of the Store data.
stopAutoPersisting(stopSaveFirst?: boolean): Promise<SqliteBunPersister>| Type | Description | |
|---|---|---|
stopSaveFirst? | boolean | Whether to stop saving before loading, defaulting to false. |
| returns | Promise<SqliteBunPersister> | A Promise containing a reference to the |
This simply runs the stopAutoLoad and stopAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
If for some reason you want to stop saving before you stop loading, pass true as the second parameter.
Example
automatically loading and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoPersisting();
console.log(persister.isAutoSaving());
// -> false
console.log(persister.isAutoLoading());
// -> false
persister.destroy();
Since
v6.1.0
Load methods
isAutoLoading
The isAutoLoading method lets you find out if the Persister is currently automatically loading its content.
isAutoLoading(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoLoading.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once.
load(initialContent?: Content | () => Content): Promise<SqliteBunPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<SqliteBunPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load method is called, data has previously been persisted and instead, that is loaded.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once, and then continuously.
startAutoLoad(initialContent?: Content | () => Content): Promise<SqliteBunPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<SqliteBunPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method first runs a single call to the load method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store.
This method is asynchronous because it starts by making a single call to the asynchronous load method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store, and loads data into it from the browser's session storage, which at first is empty (so the initialTables parameter is used). Subsequent changes to the underlying storage are then reflected in the Store (in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad method stops the automatic loading of data from storage previously started with the startAutoLoad method.
stopAutoLoad(): Promise<SqliteBunPersister>| returns | Promise<SqliteBunPersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically load, this method has no effect. This method is asynchronous.
Example
This example creates an empty Store, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
isAutoSaving
The isAutoSaving method lets you find out if the Persister is currently automatically saving its content.
isAutoSaving(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoSaving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save method takes data from the Store with which the Persister is associated and persists it into storage, once.
save(): Promise<SqliteBunPersister>| returns | Promise<SqliteBunPersister> | A Promise containing a reference to the |
|---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save method takes data from the Store with which the Persister is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<SqliteBunPersister>| returns | Promise<SqliteBunPersister> | A Promise containing a reference to the |
|---|
This method first runs a single call to the save method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave method stops the automatic save of data to storage previously started with the startAutoSave method.
stopAutoSave(): Promise<SqliteBunPersister>| returns | Promise<SqliteBunPersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically save, this method has no effect. This method is asynchronous.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
await persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
getStats
The getStats method provides a set of statistics about the Persister, and is used for debugging purposes.
getStats(): PersisterStats| returns | PersisterStats | A |
|---|
The PersisterStats object contains a count of the number of times the Persister has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister object. Remember that the startAutoLoad method invokes an explicit load when it starts, and the startAutoSave method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store and to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
createSqliteBunPersister
The createSqliteBunPersister function creates a SqliteBunPersister object that can persist the Store to a local Bun SQLite database.
createSqliteBunPersister(
store: Store | MergeableStore,
db: Database,
configOrStoreTableName?: string | DatabasePersisterConfig,
onSqlCommand?: (sql: string, params?: any[]) => void,
onIgnoredError?: (error: any) => void,
): SqliteBunPersister| Type | Description | |
|---|---|---|
store | Store | MergeableStore | The |
db | Database | The database instance that was returned from |
configOrStoreTableName? | string | DatabasePersisterConfig | A |
onSqlCommand? | (sql: string, params?: any[]) => void | An optional handler called every time the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | SqliteBunPersister | A reference to the new |
A SqliteBunPersister supports regular Store objects, and can also be used to persist the metadata of a MergeableStore when using the JSON serialization mode, as described below.
As well as providing a reference to the Store to persist, you must provide a db parameter which identifies the database instance.
A database Persister uses one of two modes: either a JSON serialization of the whole Store stored in a single row of a table (the default), or a tabular mapping of Table Ids to database table names and vice-versa).
The third argument is a DatabasePersisterConfig object that configures which of those modes to use, and settings for each. If the third argument is simply a string, it is used as the storeTableName property of the JSON serialization.
See the documentation for the DpcJson and DpcTabular types for more information on how both of those modes can be configured.
Examples
This example creates a SqliteBunPersister object and persists the Store to a local SQLite database as a JSON serialization into the my_tinybase table. It makes a change to the database directly and then reloads it back into the Store.
import {Database} from 'bun:sqlite';
import {createStore} from 'tinybase';
import {createSqliteBunPersister} from 'tinybase/persisters/persister-sqlite-bun';
const db = new Database(':memory:');
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSqliteBunPersister(store, db, 'my_tinybase');
await persister.save();
// Store will be saved to the database.
console.log(db.query('SELECT * FROM my_tinybase;').all());
// -> [{_id: '_', store: '[{"pets":{"fido":{"species":"dog"}}},{}]'}]
db.query(
'UPDATE my_tinybase SET store = ' +
`'[{"pets":{"felix":{"species":"cat"}}},{}]' WHERE _id = '_';`,
).run();
await persister.load();
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
await persister.destroy();
This example creates a SqliteBunPersister object and persists the Store to a local SQLite database with tabular mapping.
import {Database} from 'bun:sqlite';
import {createStore} from 'tinybase';
import {createSqliteBunPersister} from 'tinybase/persisters/persister-sqlite-bun';
const db = new Database(':memory:');
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSqliteBunPersister(store, db, {
mode: 'tabular',
tables: {load: {pets: 'pets'}, save: {pets: 'pets'}},
});
await persister.save();
console.log(db.query('SELECT * FROM pets;').all());
// -> [{_id: 'fido', species: 'dog'}]
db.query(`INSERT INTO pets (_id, species) VALUES ('felix', 'cat')`).run();
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
await persister.destroy();
Since
v6.1.0
persister-sqlite-wasm
The persister-sqlite-wasm module of the TinyBase project lets you save and load Store data to and from a local SQLite database (in an appropriate environment).
See also
Database Persistence guide
Since
v4.0.0
Interfaces
SqliteWasmPersister
The SqliteWasmPersister interface represents a Persister that lets you save and load Store data to and from a local SQLite database.
You should use the createSqliteWasmPersister function to create a SqliteWasmPersister object.
It is a minor extension to the Persister interface and simply provides an extra getDb method for accessing a reference to the database instance the Store is being persisted to.
Since
v4.3.14
Getter methods
getStore
The getStore method returns a reference to the underlying Store or MergeableStore that is backing this Persister object.
getStore(): Store | MergeableStore| returns | Store | MergeableStore | A reference to the |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets its reference in order to update its data.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getDb
The getDb method returns a reference to the database instance the Store is being persisted to.
getDb(): any| returns | any | A reference to the database instance. |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets the database instance back out again.
import sqlite3InitModule from '@sqlite.org/sqlite-wasm';
import {createStore} from 'tinybase';
import {createSqliteWasmPersister} from 'tinybase/persisters/persister-sqlite-wasm';
const sqlite3 = await sqlite3InitModule();
const db = new sqlite3.oo1.DB(':memory:', 'c');
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSqliteWasmPersister(
store,
sqlite3,
db,
'my_tinybase',
);
console.log(persister.getDb() == db);
// -> true
await persister.destroy();
Since
v4.3.14
Listener methods
addStatusListener
The addStatusListener method registers a listener function with the Persister that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<StoreOrMergeableStore>): string| Type | Description | |
|---|---|---|
listener | StatusListener<StoreOrMergeableStore> | The function that will be called whenever the |
| returns | string | A unique |
The provided listener is a StatusListener function, and will be called with a reference to the Persister and the new Status: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener method removes a listener that was previously added to the Persister.
delListener(listenerId: string): this| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | this | A reference to the |
Use the Id returned by whichever method was used to add the listener. Note that the Persister may re-use this Id for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
destroy
The destroy method should be called when this Persister object is no longer used.
destroy(): Promise<SqliteWasmPersister>| returns | Promise<SqliteWasmPersister> | A Promise containing a reference to the |
|---|
This guarantees that all of the listeners that the object registered with the underlying Store and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad method and the stopAutoSave method in succession. This method is asynchronous.
Example
This example creates a Store, associates a Persister object with it (that registers a TablesListener with the underlying Store), and then destroys it again, removing the listener.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
await persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus method lets you find out if the Persister is currently in the process of loading or saving content.
getStatus(): StatusIt can only be doing one or the other (or neither) at any given time. The Status enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister and queries its status.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<SqliteWasmPersister>| Type | Description | |
|---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
| returns | Promise<SqliteWasmPersister> | A reference to the |
For example, a database Persister may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister.
Example
This example creates a custom Persister object against a newly-created Store and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createStore} from 'tinybase';
import {createCustomPersister} from 'tinybase/persisters';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
() => checkRemoteSystemIsReady(),
() => sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
startAutoPersisting
The startAutoPersist method is a convenience method that starts both automatic loading and saving of the Store data.
startAutoPersisting(
initialContent?: Content | () => Content,
startSaveFirst?: boolean,
): Promise<SqliteWasmPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
startSaveFirst? | boolean | Whether to start saving before loading, defaulting to false. |
| returns | Promise<SqliteWasmPersister> | A Promise containing a reference to the |
This simply runs the startAutoLoad and startAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
This method can take initialContent to pass to the startAutoLoad method. See its documentation for more details.
If for some reason you want to start saving before you start loading, pass true as the second parameter.
Example
and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
persister.destroy();
Since
v6.1.0
stopAutoPersisting
The stopAutoPersist method is a convenience method that stops both automatic loading and saving of the Store data.
stopAutoPersisting(stopSaveFirst?: boolean): Promise<SqliteWasmPersister>| Type | Description | |
|---|---|---|
stopSaveFirst? | boolean | Whether to stop saving before loading, defaulting to false. |
| returns | Promise<SqliteWasmPersister> | A Promise containing a reference to the |
This simply runs the stopAutoLoad and stopAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
If for some reason you want to stop saving before you stop loading, pass true as the second parameter.
Example
automatically loading and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoPersisting();
console.log(persister.isAutoSaving());
// -> false
console.log(persister.isAutoLoading());
// -> false
persister.destroy();
Since
v6.1.0
Load methods
isAutoLoading
The isAutoLoading method lets you find out if the Persister is currently automatically loading its content.
isAutoLoading(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoLoading.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once.
load(initialContent?: Content | () => Content): Promise<SqliteWasmPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<SqliteWasmPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load method is called, data has previously been persisted and instead, that is loaded.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once, and then continuously.
startAutoLoad(initialContent?: Content | () => Content): Promise<SqliteWasmPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<SqliteWasmPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method first runs a single call to the load method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store.
This method is asynchronous because it starts by making a single call to the asynchronous load method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store, and loads data into it from the browser's session storage, which at first is empty (so the initialTables parameter is used). Subsequent changes to the underlying storage are then reflected in the Store (in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad method stops the automatic loading of data from storage previously started with the startAutoLoad method.
stopAutoLoad(): Promise<SqliteWasmPersister>| returns | Promise<SqliteWasmPersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically load, this method has no effect. This method is asynchronous.
Example
This example creates an empty Store, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
isAutoSaving
The isAutoSaving method lets you find out if the Persister is currently automatically saving its content.
isAutoSaving(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoSaving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save method takes data from the Store with which the Persister is associated and persists it into storage, once.
save(): Promise<SqliteWasmPersister>| returns | Promise<SqliteWasmPersister> | A Promise containing a reference to the |
|---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save method takes data from the Store with which the Persister is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<SqliteWasmPersister>| returns | Promise<SqliteWasmPersister> | A Promise containing a reference to the |
|---|
This method first runs a single call to the save method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave method stops the automatic save of data to storage previously started with the startAutoSave method.
stopAutoSave(): Promise<SqliteWasmPersister>| returns | Promise<SqliteWasmPersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically save, this method has no effect. This method is asynchronous.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
await persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
getStats
The getStats method provides a set of statistics about the Persister, and is used for debugging purposes.
getStats(): PersisterStats| returns | PersisterStats | A |
|---|
The PersisterStats object contains a count of the number of times the Persister has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister object. Remember that the startAutoLoad method invokes an explicit load when it starts, and the startAutoSave method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store and to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
createSqliteWasmPersister
The createSqliteWasmPersister function creates a SqliteWasmPersister object that can persist the Store to a local SQLite database.
createSqliteWasmPersister(
store: Store | MergeableStore,
sqlite3: any,
db: any,
configOrStoreTableName?: string | DatabasePersisterConfig,
onSqlCommand?: (sql: string, params?: any[]) => void,
onIgnoredError?: (error: any) => void,
): SqliteWasmPersister| Type | Description | |
|---|---|---|
store | Store | MergeableStore | The |
sqlite3 | any | The WASM module that was returned from |
db | any | The database instance that was returned from |
configOrStoreTableName? | string | DatabasePersisterConfig | A |
onSqlCommand? | (sql: string, params?: any[]) => void | An optional handler called every time the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | SqliteWasmPersister | A reference to the new |
A SqliteWasmPersister supports regular Store objects, and can also be used to persist the metadata of a MergeableStore when using the JSON serialization mode, as described below.
As well as providing a reference to the Store to persist, you must provide sqlite3 and db parameters which identify the WASM module and database instance respectively.
A database Persister uses one of two modes: either a JSON serialization of the whole Store stored in a single row of a table (the default), or a tabular mapping of Table Ids to database table names and vice-versa).
The fourth argument is a DatabasePersisterConfig object that configures which of those modes to use, and settings for each. If the fourth argument is simply a string, it is used as the storeTableName property of the JSON serialization.
See the documentation for the DpcJson and DpcTabular types for more information on how both of those modes can be configured.
Examples
This example creates a SqliteWasmPersister object and persists the Store to a local SQLite database as a JSON serialization into the my_tinybase table. It makes a change to the database directly and then reloads it back into the Store.
import sqlite3InitModule from '@sqlite.org/sqlite-wasm';
import {createStore} from 'tinybase';
import {createSqliteWasmPersister} from 'tinybase/persisters/persister-sqlite-wasm';
const sqlite3 = await sqlite3InitModule();
const db = new sqlite3.oo1.DB(':memory:', 'c');
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSqliteWasmPersister(
store,
sqlite3,
db,
'my_tinybase',
);
await persister.save();
// Store will be saved to the database.
console.log(db.exec('SELECT * FROM my_tinybase;', {rowMode: 'object'}));
// -> [{_id: '_', store: '[{"pets":{"fido":{"species":"dog"}}},{}]'}]
db.exec(
'UPDATE my_tinybase SET store = ' +
`'[{"pets":{"felix":{"species":"cat"}}},{}]' WHERE _id = '_';`,
);
await persister.load();
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
await persister.destroy();
This example creates a SqliteWasmPersister object and persists the Store to a local SQLite database with tabular mapping.
import sqlite3InitModule from '@sqlite.org/sqlite-wasm';
import {createStore} from 'tinybase';
import {createSqliteWasmPersister} from 'tinybase/persisters/persister-sqlite-wasm';
const sqlite3 = await sqlite3InitModule();
const db = new sqlite3.oo1.DB(':memory:', 'c');
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSqliteWasmPersister(store, sqlite3, db, {
mode: 'tabular',
tables: {load: {pets: 'pets'}, save: {pets: 'pets'}},
});
await persister.save();
console.log(db.exec('SELECT * FROM pets;', {rowMode: 'object'}));
// -> [{_id: 'fido', species: 'dog'}]
db.exec(`INSERT INTO pets (_id, species) VALUES ('felix', 'cat')`);
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
await persister.destroy();
Since
v4.0.0
persister-sqlite3
The persister-sqlite3 module of the TinyBase project lets you save and load Store data to and from a local SQLite database (in an appropriate environment).
See also
Database Persistence guide
Since
v4.0.0
Interfaces
Sqlite3Persister
The Sqlite3Persister interface represents a Persister that lets you save and load Store data to and from a local SQLite database.
You should use the createSqlite3Persister function to create a Sqlite3Persister object.
It is a minor extension to the Persister interface and simply provides an extra getDb method for accessing a reference to the database instance the Store is being persisted to.
Since
v4.3.14
Getter methods
getStore
The getStore method returns a reference to the underlying Store or MergeableStore that is backing this Persister object.
getStore(): Store | MergeableStore| returns | Store | MergeableStore | A reference to the |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets its reference in order to update its data.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getDb
The getDb method returns a reference to the database instance the Store is being persisted to.
getDb(): Database| returns | Database | A reference to the database instance. |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets the database instance back out again.
import {Database} from 'sqlite3';
import {createStore} from 'tinybase';
import {createSqlite3Persister} from 'tinybase/persisters/persister-sqlite3';
const db = new Database(':memory:');
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSqlite3Persister(store, db, 'my_tinybase');
console.log(persister.getDb() == db);
// -> true
await persister.destroy();
Since
v4.3.14
Listener methods
addStatusListener
The addStatusListener method registers a listener function with the Persister that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<StoreOrMergeableStore>): string| Type | Description | |
|---|---|---|
listener | StatusListener<StoreOrMergeableStore> | The function that will be called whenever the |
| returns | string | A unique |
The provided listener is a StatusListener function, and will be called with a reference to the Persister and the new Status: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener method removes a listener that was previously added to the Persister.
delListener(listenerId: string): this| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | this | A reference to the |
Use the Id returned by whichever method was used to add the listener. Note that the Persister may re-use this Id for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
destroy
The destroy method should be called when this Persister object is no longer used.
destroy(): Promise<Sqlite3Persister>| returns | Promise<Sqlite3Persister> | A Promise containing a reference to the |
|---|
This guarantees that all of the listeners that the object registered with the underlying Store and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad method and the stopAutoSave method in succession. This method is asynchronous.
Example
This example creates a Store, associates a Persister object with it (that registers a TablesListener with the underlying Store), and then destroys it again, removing the listener.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
await persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus method lets you find out if the Persister is currently in the process of loading or saving content.
getStatus(): StatusIt can only be doing one or the other (or neither) at any given time. The Status enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister and queries its status.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<Sqlite3Persister>| Type | Description | |
|---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
| returns | Promise<Sqlite3Persister> | A reference to the |
For example, a database Persister may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister.
Example
This example creates a custom Persister object against a newly-created Store and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createStore} from 'tinybase';
import {createCustomPersister} from 'tinybase/persisters';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
() => checkRemoteSystemIsReady(),
() => sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
startAutoPersisting
The startAutoPersist method is a convenience method that starts both automatic loading and saving of the Store data.
startAutoPersisting(
initialContent?: Content | () => Content,
startSaveFirst?: boolean,
): Promise<Sqlite3Persister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
startSaveFirst? | boolean | Whether to start saving before loading, defaulting to false. |
| returns | Promise<Sqlite3Persister> | A Promise containing a reference to the |
This simply runs the startAutoLoad and startAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
This method can take initialContent to pass to the startAutoLoad method. See its documentation for more details.
If for some reason you want to start saving before you start loading, pass true as the second parameter.
Example
and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
persister.destroy();
Since
v6.1.0
stopAutoPersisting
The stopAutoPersist method is a convenience method that stops both automatic loading and saving of the Store data.
stopAutoPersisting(stopSaveFirst?: boolean): Promise<Sqlite3Persister>| Type | Description | |
|---|---|---|
stopSaveFirst? | boolean | Whether to stop saving before loading, defaulting to false. |
| returns | Promise<Sqlite3Persister> | A Promise containing a reference to the |
This simply runs the stopAutoLoad and stopAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
If for some reason you want to stop saving before you stop loading, pass true as the second parameter.
Example
automatically loading and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoPersisting();
console.log(persister.isAutoSaving());
// -> false
console.log(persister.isAutoLoading());
// -> false
persister.destroy();
Since
v6.1.0
Load methods
isAutoLoading
The isAutoLoading method lets you find out if the Persister is currently automatically loading its content.
isAutoLoading(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoLoading.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once.
load(initialContent?: Content | () => Content): Promise<Sqlite3Persister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<Sqlite3Persister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load method is called, data has previously been persisted and instead, that is loaded.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once, and then continuously.
startAutoLoad(initialContent?: Content | () => Content): Promise<Sqlite3Persister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<Sqlite3Persister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method first runs a single call to the load method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store.
This method is asynchronous because it starts by making a single call to the asynchronous load method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store, and loads data into it from the browser's session storage, which at first is empty (so the initialTables parameter is used). Subsequent changes to the underlying storage are then reflected in the Store (in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad method stops the automatic loading of data from storage previously started with the startAutoLoad method.
stopAutoLoad(): Promise<Sqlite3Persister>| returns | Promise<Sqlite3Persister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically load, this method has no effect. This method is asynchronous.
Example
This example creates an empty Store, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
isAutoSaving
The isAutoSaving method lets you find out if the Persister is currently automatically saving its content.
isAutoSaving(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoSaving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save method takes data from the Store with which the Persister is associated and persists it into storage, once.
save(): Promise<Sqlite3Persister>| returns | Promise<Sqlite3Persister> | A Promise containing a reference to the |
|---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save method takes data from the Store with which the Persister is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<Sqlite3Persister>| returns | Promise<Sqlite3Persister> | A Promise containing a reference to the |
|---|
This method first runs a single call to the save method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave method stops the automatic save of data to storage previously started with the startAutoSave method.
stopAutoSave(): Promise<Sqlite3Persister>| returns | Promise<Sqlite3Persister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically save, this method has no effect. This method is asynchronous.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
await persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
getStats
The getStats method provides a set of statistics about the Persister, and is used for debugging purposes.
getStats(): PersisterStats| returns | PersisterStats | A |
|---|
The PersisterStats object contains a count of the number of times the Persister has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister object. Remember that the startAutoLoad method invokes an explicit load when it starts, and the startAutoSave method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store and to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
createSqlite3Persister
The createSqlite3Persister function creates a Sqlite3Persister object that can persist the Store to a local SQLite database.
createSqlite3Persister(
store: Store | MergeableStore,
db: Database,
configOrStoreTableName?: string | DatabasePersisterConfig,
onSqlCommand?: (sql: string, params?: any[]) => void,
onIgnoredError?: (error: any) => void,
): Sqlite3Persister| Type | Description | |
|---|---|---|
store | Store | MergeableStore | The |
db | Database | The database instance that was returned from |
configOrStoreTableName? | string | DatabasePersisterConfig | A |
onSqlCommand? | (sql: string, params?: any[]) => void | An optional handler called every time the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | Sqlite3Persister | A reference to the new |
A Sqlite3Persister supports regular Store objects, and can also be used to persist the metadata of a MergeableStore when using the JSON serialization mode, as described below.
As well as providing a reference to the Store to persist, you must provide a db parameter which identifies the database instance.
A database Persister uses one of two modes: either a JSON serialization of the whole Store stored in a single row of a table (the default), or a tabular mapping of Table Ids to database table names and vice-versa).
The third argument is a DatabasePersisterConfig object that configures which of those modes to use, and settings for each. If the third argument is simply a string, it is used as the storeTableName property of the JSON serialization.
See the documentation for the DpcJson and DpcTabular types for more information on how both of those modes can be configured.
Examples
This example creates a Sqlite3Persister object and persists the Store to a local SQLite database as a JSON serialization into the my_tinybase table. It makes a change to the database directly and then reloads it back into the Store.
import {Database} from 'sqlite3';
import {createStore} from 'tinybase';
import {createSqlite3Persister} from 'tinybase/persisters/persister-sqlite3';
const db = new Database(':memory:');
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSqlite3Persister(store, db, 'my_tinybase');
await persister.save();
// Store will be saved to the database.
console.log(
await new Promise((resolve) =>
db.all('SELECT * FROM my_tinybase;', (_, rows) => resolve(rows)),
),
);
// -> [{_id: '_', store: '[{"pets":{"fido":{"species":"dog"}}},{}]'}]
await new Promise((resolve) =>
db.all(
'UPDATE my_tinybase SET store = ' +
`'[{"pets":{"felix":{"species":"cat"}}},{}]' WHERE _id = '_';`,
resolve,
),
);
await persister.load();
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
await persister.destroy();
This example creates a Sqlite3Persister object and persists the Store to a local SQLite database with tabular mapping.
import {Database} from 'sqlite3';
import {createStore} from 'tinybase';
import {createSqlite3Persister} from 'tinybase/persisters/persister-sqlite3';
const db = new Database(':memory:');
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSqlite3Persister(store, db, {
mode: 'tabular',
tables: {load: {pets: 'pets'}, save: {pets: 'pets'}},
});
await persister.save();
console.log(
await new Promise((resolve) =>
db.all('SELECT * FROM pets;', (_, rows) => resolve(rows)),
),
);
// -> [{_id: 'fido', species: 'dog'}]
await new Promise((resolve) =>
db.all(
`INSERT INTO pets (_id, species) VALUES ('felix', 'cat')`,
resolve,
),
);
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
await persister.destroy();
Since
v4.0.0
persister-yjs
The persister-yjs module of the TinyBase project provides a way to save and load Store data to and from a Yjs document.
A single entry point, the createYjsPersister function, is provided, which returns a new Persister object that can bind a Store to a provided Yjs document.
See also
Since
v4.0.0
Interfaces
YjsPersister
The YjsPersister interface represents a Persister that lets you save and load Store data to and from a Yjs document.
You should use the createYjsPersister function to create a YjsPersister object.
It is a minor extension to the Persister interface and simply provides an extra getYDoc method for accessing the Yjs document the Store is being persisted to.
Since
v4.3.14
Getter methods
getStore
The getStore method returns a reference to the underlying Store or MergeableStore that is backing this Persister object.
getStore(): Store| returns | Store | A reference to the |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets its reference in order to update its data.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getYDoc
The getYDoc method returns the Yjs document the Store is being persisted to.
getYDoc(): Doc| returns | Doc | The Yjs document. |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets the Yjs document back out again.
import {createStore} from 'tinybase';
import {createYjsPersister} from 'tinybase/persisters/persister-yjs';
import {Doc} from 'yjs';
const doc = new Doc();
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createYjsPersister(store, doc);
console.log(persister.getYDoc() == doc);
// -> true
await persister.destroy();
Since
v4.3.14
Listener methods
addStatusListener
The addStatusListener method registers a listener function with the Persister that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<StoreOnly>): string| Type | Description | |
|---|---|---|
listener | StatusListener<StoreOnly> | The function that will be called whenever the |
| returns | string | A unique |
The provided listener is a StatusListener function, and will be called with a reference to the Persister and the new Status: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener method removes a listener that was previously added to the Persister.
delListener(listenerId: string): this| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | this | A reference to the |
Use the Id returned by whichever method was used to add the listener. Note that the Persister may re-use this Id for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
destroy
The destroy method should be called when this Persister object is no longer used.
destroy(): Promise<YjsPersister>| returns | Promise<YjsPersister> | A Promise containing a reference to the |
|---|
This guarantees that all of the listeners that the object registered with the underlying Store and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad method and the stopAutoSave method in succession. This method is asynchronous.
Example
This example creates a Store, associates a Persister object with it (that registers a TablesListener with the underlying Store), and then destroys it again, removing the listener.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
await persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus method lets you find out if the Persister is currently in the process of loading or saving content.
getStatus(): StatusIt can only be doing one or the other (or neither) at any given time. The Status enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister and queries its status.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<YjsPersister>| Type | Description | |
|---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
| returns | Promise<YjsPersister> | A reference to the |
For example, a database Persister may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister.
Example
This example creates a custom Persister object against a newly-created Store and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createStore} from 'tinybase';
import {createCustomPersister} from 'tinybase/persisters';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
() => checkRemoteSystemIsReady(),
() => sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
startAutoPersisting
The startAutoPersist method is a convenience method that starts both automatic loading and saving of the Store data.
startAutoPersisting(
initialContent?: Content | () => Content,
startSaveFirst?: boolean,
): Promise<YjsPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
startSaveFirst? | boolean | Whether to start saving before loading, defaulting to false. |
| returns | Promise<YjsPersister> | A Promise containing a reference to the |
This simply runs the startAutoLoad and startAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
This method can take initialContent to pass to the startAutoLoad method. See its documentation for more details.
If for some reason you want to start saving before you start loading, pass true as the second parameter.
Example
and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
persister.destroy();
Since
v6.1.0
stopAutoPersisting
The stopAutoPersist method is a convenience method that stops both automatic loading and saving of the Store data.
stopAutoPersisting(stopSaveFirst?: boolean): Promise<YjsPersister>| Type | Description | |
|---|---|---|
stopSaveFirst? | boolean | Whether to stop saving before loading, defaulting to false. |
| returns | Promise<YjsPersister> | A Promise containing a reference to the |
This simply runs the stopAutoLoad and stopAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
If for some reason you want to stop saving before you stop loading, pass true as the second parameter.
Example
automatically loading and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoPersisting();
console.log(persister.isAutoSaving());
// -> false
console.log(persister.isAutoLoading());
// -> false
persister.destroy();
Since
v6.1.0
Load methods
isAutoLoading
The isAutoLoading method lets you find out if the Persister is currently automatically loading its content.
isAutoLoading(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoLoading.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once.
load(initialContent?: Content | () => Content): Promise<YjsPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<YjsPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load method is called, data has previously been persisted and instead, that is loaded.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once, and then continuously.
startAutoLoad(initialContent?: Content | () => Content): Promise<YjsPersister>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<YjsPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method first runs a single call to the load method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store.
This method is asynchronous because it starts by making a single call to the asynchronous load method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store, and loads data into it from the browser's session storage, which at first is empty (so the initialTables parameter is used). Subsequent changes to the underlying storage are then reflected in the Store (in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad method stops the automatic loading of data from storage previously started with the startAutoLoad method.
stopAutoLoad(): Promise<YjsPersister>| returns | Promise<YjsPersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically load, this method has no effect. This method is asynchronous.
Example
This example creates an empty Store, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
isAutoSaving
The isAutoSaving method lets you find out if the Persister is currently automatically saving its content.
isAutoSaving(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoSaving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save method takes data from the Store with which the Persister is associated and persists it into storage, once.
save(): Promise<YjsPersister>| returns | Promise<YjsPersister> | A Promise containing a reference to the |
|---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save method takes data from the Store with which the Persister is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<YjsPersister>| returns | Promise<YjsPersister> | A Promise containing a reference to the |
|---|
This method first runs a single call to the save method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave method stops the automatic save of data to storage previously started with the startAutoSave method.
stopAutoSave(): Promise<YjsPersister>| returns | Promise<YjsPersister> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically save, this method has no effect. This method is asynchronous.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
await persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
getStats
The getStats method provides a set of statistics about the Persister, and is used for debugging purposes.
getStats(): PersisterStats| returns | PersisterStats | A |
|---|
The PersisterStats object contains a count of the number of times the Persister has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister object. Remember that the startAutoLoad method invokes an explicit load when it starts, and the startAutoSave method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store and to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
createYjsPersister
The createYjsPersister function creates a YjsPersister object that can persist the Store to a Yjs document.
createYjsPersister(
store: Store,
yDoc: Doc,
yMapName?: string,
onIgnoredError?: (error: any) => void,
): YjsPersister| Type | Description | |
|---|---|---|
store | Store | The |
yDoc | Doc | The Yjs document to persist the |
yMapName? | string | The name of the Y.Map used inside the Yjs document to sync the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | YjsPersister | A reference to the new |
A YjsPersister only supports regular Store objects, and cannot be used to persist the metadata of a MergeableStore.
As well as providing a reference to the Store to persist, you must provide the Yjs document to persist it to.
Examples
This example creates a YjsPersister object and persists the Store to a Yjs document.
import {createStore} from 'tinybase';
import {createYjsPersister} from 'tinybase/persisters/persister-yjs';
import {Doc} from 'yjs';
const doc = new Doc();
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createYjsPersister(store, doc);
await persister.save();
// Store will be saved to the document.
console.log(doc.toJSON());
// -> {tinybase: {t: {pets: {fido: {species: 'dog'}}}, v: {}}}
await persister.destroy();
This more complex example uses Yjs updates to keep two Store objects (each with their own YjsPersister objects and Yjs documents) in sync with each other. We use the await keyword extensively for the purpose of ensuring sequentiality in this example.
Typically, real-world synchronization would happen between two systems via a Yjs connection provider. Here, we synthesize that with the syncDocs function.
import {createStore} from 'tinybase';
import {createYjsPersister} from 'tinybase/persisters/persister-yjs';
import {Doc, applyUpdate, encodeStateAsUpdate} from 'yjs';
const doc1 = new Doc();
const doc2 = new Doc();
// A function to manually synchronize documents with each other. Typically
// this would happen over the wire, via a Yjs connection provider.
const syncDocs = async () => {
applyUpdate(doc1, encodeStateAsUpdate(doc2));
applyUpdate(doc2, encodeStateAsUpdate(doc1));
};
// Bind a persisted Store to each document.
const store1 = createStore();
const persister1 = createYjsPersister(store1, doc1);
await persister1.startAutoLoad();
await persister1.startAutoSave();
const store2 = createStore();
const persister2 = createYjsPersister(store2, doc2);
await persister2.startAutoLoad();
await persister2.startAutoSave();
// Synchronize the documents in their initial state.
await syncDocs();
// Make a change to each of the two Stores.
store1.setTables({pets: {fido: {species: 'dog'}}});
store2.setValues({open: true});
// ...
// Synchronize the documents with each other again.
await syncDocs();
// Ensure the Stores are in sync.
console.log(store1.getContent());
// -> [{pets: {fido: {species: 'dog'}}}, {open: true}]
console.log(store2.getContent());
// -> [{pets: {fido: {species: 'dog'}}}, {open: true}]
await persister1.destroy();
await persister2.destroy();
Since
v4.0.0
synchronizers
The synchronizers module of the TinyBase project lets you synchronize MergeableStore data to and from other MergeableStore instances.
See also
Synchronization guide
Since
v5.0.0
Interfaces
Synchronizer
A Synchronizer object lets you synchronize MergeableStore data with another TinyBase client or system.
This is useful for sharing data between users, or between devices of a single user. This is especially valuable when there is the possibility that there has been a lack of immediate connectivity between clients and the synchronization requires some negotiation to orchestrate merging the MergeableStore objects together.
Creating a Synchronizer depends on the choice of underlying medium over which the synchronization will take place. Options include the createWsSynchronizer function (for a Synchronizer that will sync over web-sockets), and the createLocalSynchronizer function (for a Synchronizer that will sync two MergeableStore objects in memory on one system). The createCustomSynchronizer function can also be used to easily create a fully customized way to send and receive the messages of the synchronization protocol.
Note that, as an interface, it is an extension to the Persister interface, since they share underlying implementations. Think of a Synchronizer as 'persisting' your MergeableStore to another client (and vice-versa).
Example
This example creates two empty MergeableStore objects, creates a LocalSynchronizer for each, and starts synchronizing them. A change in one becomes evident in the other.
import {createMergeableStore} from 'tinybase';
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
const store1 = createMergeableStore();
const store2 = createMergeableStore();
const synchronizer1 = createLocalSynchronizer(store1);
const synchronizer2 = createLocalSynchronizer(store2);
await synchronizer1.startSync();
await synchronizer2.startSync();
store1.setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}}}
store2.setRow('pets', 'felix', {species: 'cat'});
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
await synchronizer1.destroy();
await synchronizer2.destroy();
Since
v5.0.0
Getter methods
getStore
The getStore method returns a reference to the underlying Store or MergeableStore that is backing this Persister object.
getStore(): MergeableStore| returns | MergeableStore | A reference to the |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets its reference in order to update its data.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
Listener methods
addStatusListener
The addStatusListener method registers a listener function with the Persister that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<MergeableStoreOnly>): string| Type | Description | |
|---|---|---|
listener | StatusListener<MergeableStoreOnly> | The function that will be called whenever the |
| returns | string | A unique |
The provided listener is a StatusListener function, and will be called with a reference to the Persister and the new Status: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener method removes a listener that was previously added to the Persister.
delListener(listenerId: string): this| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | this | A reference to the |
Use the Id returned by whichever method was used to add the listener. Note that the Persister may re-use this Id for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
destroy
The destroy method should be called when this Persister object is no longer used.
destroy(): Promise<Synchronizer>| returns | Promise<Synchronizer> | A Promise containing a reference to the |
|---|
This guarantees that all of the listeners that the object registered with the underlying Store and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad method and the stopAutoSave method in succession. This method is asynchronous.
Example
This example creates a Store, associates a Persister object with it (that registers a TablesListener with the underlying Store), and then destroys it again, removing the listener.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
await persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus method lets you find out if the Persister is currently in the process of loading or saving content.
getStatus(): StatusIt can only be doing one or the other (or neither) at any given time. The Status enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister and queries its status.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<Synchronizer>| Type | Description | |
|---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
| returns | Promise<Synchronizer> | A reference to the |
For example, a database Persister may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister.
Example
This example creates a custom Persister object against a newly-created Store and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createStore} from 'tinybase';
import {createCustomPersister} from 'tinybase/persisters';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
() => checkRemoteSystemIsReady(),
() => sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
startAutoPersisting
The startAutoPersist method is a convenience method that starts both automatic loading and saving of the Store data.
startAutoPersisting(
initialContent?: Content | () => Content,
startSaveFirst?: boolean,
): Promise<Synchronizer>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
startSaveFirst? | boolean | Whether to start saving before loading, defaulting to false. |
| returns | Promise<Synchronizer> | A Promise containing a reference to the |
This simply runs the startAutoLoad and startAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
This method can take initialContent to pass to the startAutoLoad method. See its documentation for more details.
If for some reason you want to start saving before you start loading, pass true as the second parameter.
Example
and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
persister.destroy();
Since
v6.1.0
stopAutoPersisting
The stopAutoPersist method is a convenience method that stops both automatic loading and saving of the Store data.
stopAutoPersisting(stopSaveFirst?: boolean): Promise<Synchronizer>| Type | Description | |
|---|---|---|
stopSaveFirst? | boolean | Whether to stop saving before loading, defaulting to false. |
| returns | Promise<Synchronizer> | A Promise containing a reference to the |
This simply runs the stopAutoLoad and stopAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
If for some reason you want to stop saving before you stop loading, pass true as the second parameter.
Example
automatically loading and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoPersisting();
console.log(persister.isAutoSaving());
// -> false
console.log(persister.isAutoLoading());
// -> false
persister.destroy();
Since
v6.1.0
Load methods
isAutoLoading
The isAutoLoading method lets you find out if the Persister is currently automatically loading its content.
isAutoLoading(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoLoading.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once.
load(initialContent?: Content | () => Content): Promise<Synchronizer>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<Synchronizer> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load method is called, data has previously been persisted and instead, that is loaded.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once, and then continuously.
startAutoLoad(initialContent?: Content | () => Content): Promise<Synchronizer>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<Synchronizer> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method first runs a single call to the load method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store.
This method is asynchronous because it starts by making a single call to the asynchronous load method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store, and loads data into it from the browser's session storage, which at first is empty (so the initialTables parameter is used). Subsequent changes to the underlying storage are then reflected in the Store (in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad method stops the automatic loading of data from storage previously started with the startAutoLoad method.
stopAutoLoad(): Promise<Synchronizer>| returns | Promise<Synchronizer> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically load, this method has no effect. This method is asynchronous.
Example
This example creates an empty Store, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
isAutoSaving
The isAutoSaving method lets you find out if the Persister is currently automatically saving its content.
isAutoSaving(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoSaving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save method takes data from the Store with which the Persister is associated and persists it into storage, once.
save(): Promise<Synchronizer>| returns | Promise<Synchronizer> | A Promise containing a reference to the |
|---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save method takes data from the Store with which the Persister is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<Synchronizer>| returns | Promise<Synchronizer> | A Promise containing a reference to the |
|---|
This method first runs a single call to the save method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave method stops the automatic save of data to storage previously started with the startAutoSave method.
stopAutoSave(): Promise<Synchronizer>| returns | Promise<Synchronizer> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically save, this method has no effect. This method is asynchronous.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
await persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Synchronization methods
getSynchronizerStats
The getSynchronizerStats method provides a set of statistics about the Synchronizer, and is used for debugging purposes.
getSynchronizerStats(): SynchronizerStats| returns | SynchronizerStats | A |
|---|
The SynchronizerStats object contains a count of the number of times the Synchronizer has sent and received messages.
The method is intended to be used during development to ensure your synchronization layer is acting as expected, for example.
Example
This example gets the send and receive statistics of two active Synchronizer instances.
import {createMergeableStore} from 'tinybase';
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
const store1 = createMergeableStore();
const store2 = createMergeableStore();
const synchronizer1 = createLocalSynchronizer(store1);
const synchronizer2 = createLocalSynchronizer(store2);
await synchronizer1.startSync();
await synchronizer2.startSync();
store1.setTables({pets: {fido: {species: 'dog'}}});
// ...
store2.setRow('pets', 'felix', {species: 'cat'});
// ...
console.log(synchronizer1.getSynchronizerStats());
// -> {receives: 4, sends: 5}
console.log(synchronizer2.getSynchronizerStats());
// -> {receives: 5, sends: 4}
await synchronizer1.destroy();
await synchronizer2.destroy();
Since
v5.0.0
startSync
The startSync method is used to start the process of synchronization between this instance and another matching Synchronizer.
startSync(initialContent?: Content): Promise<Synchronizer>| Type | Description | |
|---|---|---|
initialContent? | Content | An optional |
| returns | Promise<Synchronizer> | A Promise containing a reference to the |
The underlying implementation of a Synchronizer is shared with the Persister framework, and so this startSync method is equivalent to starting both auto-loading (listening to sync messages from other active Synchronizer instances) and auto-saving (sending sync messages to it).
This method is asynchronous so you should you await calls to this method or handle the return type natively as a Promise.
Examples
This example creates two empty MergeableStore objects, creates a LocalSynchronizer for each, and starts synchronizing them. A change in one becomes evident in the other.
import {createMergeableStore} from 'tinybase';
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
const store1 = createMergeableStore();
const store2 = createMergeableStore();
const synchronizer1 = createLocalSynchronizer(store1);
const synchronizer2 = createLocalSynchronizer(store2);
await synchronizer1.startSync();
await synchronizer2.startSync();
store1.setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}}}
store2.setRow('pets', 'felix', {species: 'cat'});
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
await synchronizer1.destroy();
await synchronizer2.destroy();
This example creates two empty MergeableStore objects, creates a LocalSynchronizer for each, and starts synchronizing them with default content. The default content from the first Synchronizer's startSync method ends up populated in both MergeableStore instances: by the time the second started, the first was available to synchronize with and its default was not needed.
import {createMergeableStore} from 'tinybase';
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
const store1 = createMergeableStore();
const store2 = createMergeableStore();
const synchronizer1 = createLocalSynchronizer(store1);
const synchronizer2 = createLocalSynchronizer(store2);
await synchronizer1.startSync([{pets: {fido: {species: 'dog'}}}, {}]);
await synchronizer2.startSync([{pets: {felix: {species: 'cat'}}}, {}]);
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog'}}}
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}}}
await synchronizer1.destroy();
await synchronizer2.destroy();
Since
v5.0.0
stopSync
The stopSync method is used to stop the process of synchronization between this instance and another matching Synchronizer.
stopSync(): Promise<Synchronizer>| returns | Promise<Synchronizer> | A Promise containing a reference to the |
|---|
The underlying implementation of a Synchronizer is shared with the Persister framework, and so this startSync method is equivalent to stopping both auto-loading (listening to sync messages from other active Synchronizer instances) and auto-saving (sending sync messages to them).
This method is asynchronous.
Example
This example creates two empty MergeableStore objects, creates a LocalSynchronizer for each, and starts - then stops - synchronizing them. Subsequent changes are not merged.
import {createMergeableStore} from 'tinybase';
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
const store1 = createMergeableStore();
const synchronizer1 = createLocalSynchronizer(store1);
await synchronizer1.startSync();
const store2 = createMergeableStore();
const synchronizer2 = createLocalSynchronizer(store2);
await synchronizer2.startSync();
store1.setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog'}}}
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}}}
await synchronizer1.stopSync();
await synchronizer2.stopSync();
store1.setCell('pets', 'fido', 'color', 'brown');
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog', color: 'brown'}}}
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}}}
await synchronizer1.destroy();
await synchronizer2.destroy();
Since
v5.0.0
Development methods
getStats
The getStats method provides a set of statistics about the Persister, and is used for debugging purposes.
getStats(): PersisterStats| returns | PersisterStats | A |
|---|
The PersisterStats object contains a count of the number of times the Persister has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister object. Remember that the startAutoLoad method invokes an explicit load when it starts, and the startAutoSave method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store and to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Enumerations
Message
The Message enum is used to indicate the type of the message being passed between Synchronizer instances.
{
Response: 0;
GetContentHashes: 1;
ContentHashes: 2;
ContentDiff: 3;
GetTableDiff: 4;
GetRowDiff: 5;
GetCellDiff: 6;
GetValueDiff: 7;
}| Value | Description | |
|---|---|---|
Response | 0 | A message that is a response to a previous request. |
GetContentHashes | 1 | A message that is a request to get |
ContentHashes | 2 | A message that contains |
ContentDiff | 3 | A message that contains a ContentDiff. |
GetTableDiff | 4 | A message that is a request to get a TableDiff from another |
GetRowDiff | 5 | A message that is a request to get a RowDiff from another |
GetCellDiff | 6 | A message that is a request to get a CellDiff from another |
GetValueDiff | 7 | A message that is a request to get a ValueDiff from another |
These message comprise the basic synchronization protocol for merging MergeableStore instances across multiple systems.
The enum is generally intended to be used internally within TinyBase itself and opaque to applications that use synchronization.
Since
v5.0.0
Functions
createCustomSynchronizer
The createCustomSynchronizer function creates a Synchronizer object that can persist one MergeableStore to another.
createCustomSynchronizer(
store: MergeableStore,
send: Send,
registerReceive: (receive: Receive) => void,
destroy: () => void,
requestTimeoutSeconds: number,
onSend?: Send,
onReceive?: Receive,
onIgnoredError?: (error: any) => void,
): Synchronizer| Type | Description | |
|---|---|---|
store | MergeableStore | The |
send | Send | A |
registerReceive | (receive: Receive) => void | A callback (called once when the |
destroy | () => void | A function called when destroying the |
requestTimeoutSeconds | number | An number of seconds before a request sent from this |
onSend? | Send | An optional handler for the messages that this |
onReceive? | Receive | An optional handler for the messages that this |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | Synchronizer | A reference to the new |
As well as providing a reference to the MergeableStore to synchronize, you must provide parameters which identify how to send and receive changes to and from this MergeableStore and its peers. This is entirely dependent upon the medium of communication used.
You must also provide a callback for when the Synchronizer is destroyed (which is a good place to clean up resources and stop communication listeners), and indicate how long the Synchronizer will wait for responses to message requests before timing out.
A final set of optional handlers can be provided to help debug sends, receives, and errors respectively.
Example
This example creates a function for creating custom Synchronizer objects via a very naive pair of message buses (which are first-in, first-out). Each Synchronizer can write to the other's bus, and they each poll to read from their own. The example then uses these Synchronizer instances to sync two MergeableStore objects together
import {createMergeableStore, getUniqueId} from 'tinybase';
import {createCustomSynchronizer} from 'tinybase/synchronizers';
const bus1 = [];
const bus2 = [];
const createBusSynchronizer = (store, localBus, remoteBus) => {
let timer;
const clientId = getUniqueId();
return createCustomSynchronizer(
store,
(toClientId, requestId, message, body) => {
// send
remoteBus.push([clientId, requestId, message, body]);
},
(receive) => {
// registerReceive
timer = setInterval(() => {
if (localBus.length > 0) {
receive(...localBus.shift());
}
}, 1);
},
() => clearInterval(timer), // destroy
1,
);
};
const store1 = createMergeableStore();
const store2 = createMergeableStore();
const synchronizer1 = createBusSynchronizer(store1, bus1, bus2);
const synchronizer2 = createBusSynchronizer(store2, bus2, bus1);
await synchronizer1.startSync();
await synchronizer2.startSync();
store1.setTables({pets: {fido: {species: 'dog'}}});
store2.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
await synchronizer1.destroy();
await synchronizer2.destroy();
Since
v5.0.0
Type Aliases
Synchronization type aliases
Receive
The Receive type describes a function that knows how to handle the arrival of a message as part of the synchronization protocol.
(
fromClientId: Id,
requestId: IdOrNull,
message: Message,
body: any,
): void| Type | Description | |
|---|---|---|
fromClientId | Id | The |
requestId | IdOrNull | The optional |
message | Message | A number that indicates the type of the message, according to the |
body | any | A message-specific payload. |
| returns | void | This has no return value. |
When a message arrives (most likely from another system), the function will be called with parameters that indicate where the message came from, and its meaning and content.
Since
v5.0.0
Send
The Send type describes a function that knows how to dispatch a message as part of the synchronization protocol.
(
toClientId: IdOrNull,
requestId: IdOrNull,
message: Message,
body: any,
): void| Type | Description | |
|---|---|---|
toClientId | IdOrNull | The optional |
requestId | IdOrNull | The optional |
message | Message | A number that indicates the type of the message, according to the |
body | any | A message-specific payload. |
| returns | void | This has no return value. |
Since
v5.0.0
Development type aliases
SynchronizerStats
The SynchronizerStats type describes the number of times a Synchronizer object has sent or received data.
{
sends: number;
receives: number;
}| Type | Description | |
|---|---|---|
sends | number | The number of times messages have been sent. |
receives | number | The number of times messages has been received. |
A SynchronizerStats object is returned from the getSynchronizerStats method.
Since
v5.0.0
synchronizer-broadcast-channel
The synchronizer-broadcast-channel module of the TinyBase project lets you synchronize MergeableStore data to and from other MergeableStore instances via a browser's BroadcastChannel API.
See also
Synchronization guide
Since
v5.0.0
Interfaces
BroadcastChannelSynchronizer
The BroadcastChannelSynchronizer interface represents a Synchronizer that lets you synchronize MergeableStore data to and from other MergeableStore instances via a browser's BroadcastChannel API.
You should use the createBroadcastChannelSynchronizer function to create a BroadcastChannelSynchronizer object.
It is a minor extension to the Synchronizer interface and simply provides an extra getChannelName method for accessing the name of the channel being used.
Since
v5.0.0
Getter methods
getStore
The getStore method returns a reference to the underlying Store or MergeableStore that is backing this Persister object.
getStore(): MergeableStore| returns | MergeableStore | A reference to the |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets its reference in order to update its data.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getChannelName
The getChannelName method returns the name of the channel being used for synchronization.
getChannelName(): string| returns | string | The channel name. |
|---|
Example
This example creates a BroadcastChannelSynchronizer object for a newly-created MergeableStore and then gets the channel name back out again.
import {createMergeableStore} from 'tinybase';
import {createBroadcastChannelSynchronizer} from 'tinybase/synchronizers/synchronizer-broadcast-channel';
const store = createMergeableStore();
const synchronizer = createBroadcastChannelSynchronizer(store, 'channelA');
console.log(synchronizer.getChannelName());
// -> 'channelA'
await synchronizer.destroy();
Since
v5.0.0
Listener methods
addStatusListener
The addStatusListener method registers a listener function with the Persister that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<MergeableStoreOnly>): string| Type | Description | |
|---|---|---|
listener | StatusListener<MergeableStoreOnly> | The function that will be called whenever the |
| returns | string | A unique |
The provided listener is a StatusListener function, and will be called with a reference to the Persister and the new Status: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener method removes a listener that was previously added to the Persister.
delListener(listenerId: string): this| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | this | A reference to the |
Use the Id returned by whichever method was used to add the listener. Note that the Persister may re-use this Id for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
destroy
The destroy method should be called when this Persister object is no longer used.
destroy(): Promise<BroadcastChannelSynchronizer>| returns | Promise<BroadcastChannelSynchronizer> | A Promise containing a reference to the |
|---|
This guarantees that all of the listeners that the object registered with the underlying Store and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad method and the stopAutoSave method in succession. This method is asynchronous.
Example
This example creates a Store, associates a Persister object with it (that registers a TablesListener with the underlying Store), and then destroys it again, removing the listener.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
await persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus method lets you find out if the Persister is currently in the process of loading or saving content.
getStatus(): StatusIt can only be doing one or the other (or neither) at any given time. The Status enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister and queries its status.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<BroadcastChannelSynchronizer>| Type | Description | |
|---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
| returns | Promise<BroadcastChannelSynchronizer> | A reference to the |
For example, a database Persister may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister.
Example
This example creates a custom Persister object against a newly-created Store and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createStore} from 'tinybase';
import {createCustomPersister} from 'tinybase/persisters';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
() => checkRemoteSystemIsReady(),
() => sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
startAutoPersisting
The startAutoPersist method is a convenience method that starts both automatic loading and saving of the Store data.
startAutoPersisting(
initialContent?: Content | () => Content,
startSaveFirst?: boolean,
): Promise<BroadcastChannelSynchronizer>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
startSaveFirst? | boolean | Whether to start saving before loading, defaulting to false. |
| returns | Promise<BroadcastChannelSynchronizer> | A Promise containing a reference to the |
This simply runs the startAutoLoad and startAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
This method can take initialContent to pass to the startAutoLoad method. See its documentation for more details.
If for some reason you want to start saving before you start loading, pass true as the second parameter.
Example
and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
persister.destroy();
Since
v6.1.0
stopAutoPersisting
The stopAutoPersist method is a convenience method that stops both automatic loading and saving of the Store data.
stopAutoPersisting(stopSaveFirst?: boolean): Promise<BroadcastChannelSynchronizer>| Type | Description | |
|---|---|---|
stopSaveFirst? | boolean | Whether to stop saving before loading, defaulting to false. |
| returns | Promise<BroadcastChannelSynchronizer> | A Promise containing a reference to the |
This simply runs the stopAutoLoad and stopAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
If for some reason you want to stop saving before you stop loading, pass true as the second parameter.
Example
automatically loading and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoPersisting();
console.log(persister.isAutoSaving());
// -> false
console.log(persister.isAutoLoading());
// -> false
persister.destroy();
Since
v6.1.0
Load methods
isAutoLoading
The isAutoLoading method lets you find out if the Persister is currently automatically loading its content.
isAutoLoading(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoLoading.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once.
load(initialContent?: Content | () => Content): Promise<BroadcastChannelSynchronizer>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<BroadcastChannelSynchronizer> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load method is called, data has previously been persisted and instead, that is loaded.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once, and then continuously.
startAutoLoad(initialContent?: Content | () => Content): Promise<BroadcastChannelSynchronizer>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<BroadcastChannelSynchronizer> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method first runs a single call to the load method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store.
This method is asynchronous because it starts by making a single call to the asynchronous load method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store, and loads data into it from the browser's session storage, which at first is empty (so the initialTables parameter is used). Subsequent changes to the underlying storage are then reflected in the Store (in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad method stops the automatic loading of data from storage previously started with the startAutoLoad method.
stopAutoLoad(): Promise<BroadcastChannelSynchronizer>| returns | Promise<BroadcastChannelSynchronizer> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically load, this method has no effect. This method is asynchronous.
Example
This example creates an empty Store, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
isAutoSaving
The isAutoSaving method lets you find out if the Persister is currently automatically saving its content.
isAutoSaving(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoSaving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save method takes data from the Store with which the Persister is associated and persists it into storage, once.
save(): Promise<BroadcastChannelSynchronizer>| returns | Promise<BroadcastChannelSynchronizer> | A Promise containing a reference to the |
|---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save method takes data from the Store with which the Persister is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<BroadcastChannelSynchronizer>| returns | Promise<BroadcastChannelSynchronizer> | A Promise containing a reference to the |
|---|
This method first runs a single call to the save method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave method stops the automatic save of data to storage previously started with the startAutoSave method.
stopAutoSave(): Promise<BroadcastChannelSynchronizer>| returns | Promise<BroadcastChannelSynchronizer> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically save, this method has no effect. This method is asynchronous.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
await persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Synchronization methods
getSynchronizerStats
The getSynchronizerStats method provides a set of statistics about the Synchronizer, and is used for debugging purposes.
getSynchronizerStats(): SynchronizerStats| returns | SynchronizerStats | A |
|---|
The SynchronizerStats object contains a count of the number of times the Synchronizer has sent and received messages.
The method is intended to be used during development to ensure your synchronization layer is acting as expected, for example.
Example
This example gets the send and receive statistics of two active Synchronizer instances.
import {createMergeableStore} from 'tinybase';
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
const store1 = createMergeableStore();
const store2 = createMergeableStore();
const synchronizer1 = createLocalSynchronizer(store1);
const synchronizer2 = createLocalSynchronizer(store2);
await synchronizer1.startSync();
await synchronizer2.startSync();
store1.setTables({pets: {fido: {species: 'dog'}}});
// ...
store2.setRow('pets', 'felix', {species: 'cat'});
// ...
console.log(synchronizer1.getSynchronizerStats());
// -> {receives: 4, sends: 5}
console.log(synchronizer2.getSynchronizerStats());
// -> {receives: 5, sends: 4}
await synchronizer1.destroy();
await synchronizer2.destroy();
Since
v5.0.0
startSync
The startSync method is used to start the process of synchronization between this instance and another matching Synchronizer.
startSync(initialContent?: Content): Promise<BroadcastChannelSynchronizer>| Type | Description | |
|---|---|---|
initialContent? | Content | An optional |
| returns | Promise<BroadcastChannelSynchronizer> | A Promise containing a reference to the |
The underlying implementation of a Synchronizer is shared with the Persister framework, and so this startSync method is equivalent to starting both auto-loading (listening to sync messages from other active Synchronizer instances) and auto-saving (sending sync messages to it).
This method is asynchronous so you should you await calls to this method or handle the return type natively as a Promise.
Examples
This example creates two empty MergeableStore objects, creates a LocalSynchronizer for each, and starts synchronizing them. A change in one becomes evident in the other.
import {createMergeableStore} from 'tinybase';
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
const store1 = createMergeableStore();
const store2 = createMergeableStore();
const synchronizer1 = createLocalSynchronizer(store1);
const synchronizer2 = createLocalSynchronizer(store2);
await synchronizer1.startSync();
await synchronizer2.startSync();
store1.setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}}}
store2.setRow('pets', 'felix', {species: 'cat'});
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
await synchronizer1.destroy();
await synchronizer2.destroy();
This example creates two empty MergeableStore objects, creates a LocalSynchronizer for each, and starts synchronizing them with default content. The default content from the first Synchronizer's startSync method ends up populated in both MergeableStore instances: by the time the second started, the first was available to synchronize with and its default was not needed.
import {createMergeableStore} from 'tinybase';
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
const store1 = createMergeableStore();
const store2 = createMergeableStore();
const synchronizer1 = createLocalSynchronizer(store1);
const synchronizer2 = createLocalSynchronizer(store2);
await synchronizer1.startSync([{pets: {fido: {species: 'dog'}}}, {}]);
await synchronizer2.startSync([{pets: {felix: {species: 'cat'}}}, {}]);
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog'}}}
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}}}
await synchronizer1.destroy();
await synchronizer2.destroy();
Since
v5.0.0
stopSync
The stopSync method is used to stop the process of synchronization between this instance and another matching Synchronizer.
stopSync(): Promise<BroadcastChannelSynchronizer>| returns | Promise<BroadcastChannelSynchronizer> | A Promise containing a reference to the |
|---|
The underlying implementation of a Synchronizer is shared with the Persister framework, and so this startSync method is equivalent to stopping both auto-loading (listening to sync messages from other active Synchronizer instances) and auto-saving (sending sync messages to them).
This method is asynchronous.
Example
This example creates two empty MergeableStore objects, creates a LocalSynchronizer for each, and starts - then stops - synchronizing them. Subsequent changes are not merged.
import {createMergeableStore} from 'tinybase';
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
const store1 = createMergeableStore();
const synchronizer1 = createLocalSynchronizer(store1);
await synchronizer1.startSync();
const store2 = createMergeableStore();
const synchronizer2 = createLocalSynchronizer(store2);
await synchronizer2.startSync();
store1.setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog'}}}
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}}}
await synchronizer1.stopSync();
await synchronizer2.stopSync();
store1.setCell('pets', 'fido', 'color', 'brown');
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog', color: 'brown'}}}
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}}}
await synchronizer1.destroy();
await synchronizer2.destroy();
Since
v5.0.0
Development methods
getStats
The getStats method provides a set of statistics about the Persister, and is used for debugging purposes.
getStats(): PersisterStats| returns | PersisterStats | A |
|---|
The PersisterStats object contains a count of the number of times the Persister has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister object. Remember that the startAutoLoad method invokes an explicit load when it starts, and the startAutoSave method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store and to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
createBroadcastChannelSynchronizer
The createBroadcastChannelSynchronizer function creates a BroadcastChannelSynchronizer object that can synchronize MergeableStore data to and from other MergeableStore instances via a browser's BroadcastChannel API.
createBroadcastChannelSynchronizer(
store: MergeableStore,
channelName: string,
onSend?: Send,
onReceive?: Receive,
onIgnoredError?: (error: any) => void,
): BroadcastChannelSynchronizer| Type | Description | |
|---|---|---|
store | MergeableStore | The |
channelName | string | The name of the channel to use. |
onSend? | Send | An optional handler for the messages that this |
onReceive? | Receive | An optional handler for the messages that this |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | BroadcastChannelSynchronizer | A reference to the new |
As well as providing a reference to the MergeableStore to persist, you must provide a channel name, used by all the browser tabs, workers, or contexts that need to synchronize together.
A final set of optional handlers can be provided to help debug sends, receives, and errors respectively.
Example
This example creates two BroadcastChannelSynchronizer objects to synchronize one MergeableStore to another.
import {createMergeableStore} from 'tinybase';
import {createBroadcastChannelSynchronizer} from 'tinybase/synchronizers/synchronizer-broadcast-channel';
const store1 = createMergeableStore();
const store2 = createMergeableStore();
const synchronizer1 = createBroadcastChannelSynchronizer(
store1,
'channelA',
);
const synchronizer2 = createBroadcastChannelSynchronizer(
store2,
'channelA',
);
await synchronizer1.startSync();
await synchronizer2.startSync();
store1.setTables({pets: {fido: {species: 'dog'}}});
store2.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
await synchronizer1.destroy();
await synchronizer2.destroy();
Since
v5.0.0
synchronizer-local
The synchronizer-local module of the TinyBase project lets you synchronize MergeableStore data to and from other MergeableStore instances on the same local machine.
See also
Synchronization guide
Since
v5.0.0
Interfaces
LocalSynchronizer
The LocalSynchronizer interface represents a Synchronizer that lets you synchronize MergeableStore data to and from other MergeableStore instances on the same local machine.
You should use the createLocalSynchronizer function to create a LocalSynchronizer object.
Having no specialized methods, it is a synonym for the Synchronizer interface. This is also something of a showcase Synchronizer, rather than something you would use in a production environment. If you do need to synchronize two in-memory MergeableStore instances, you may prefer to use the merge function on either one of them instead of going to the effort of setting up this Synchronizer.
Since
v5.0.0
Getter methods
getStore
The getStore method returns a reference to the underlying Store or MergeableStore that is backing this Persister object.
getStore(): MergeableStore| returns | MergeableStore | A reference to the |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets its reference in order to update its data.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
Listener methods
addStatusListener
The addStatusListener method registers a listener function with the Persister that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<MergeableStoreOnly>): string| Type | Description | |
|---|---|---|
listener | StatusListener<MergeableStoreOnly> | The function that will be called whenever the |
| returns | string | A unique |
The provided listener is a StatusListener function, and will be called with a reference to the Persister and the new Status: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener method removes a listener that was previously added to the Persister.
delListener(listenerId: string): this| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | this | A reference to the |
Use the Id returned by whichever method was used to add the listener. Note that the Persister may re-use this Id for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
destroy
The destroy method should be called when this Persister object is no longer used.
destroy(): Promise<LocalSynchronizer>| returns | Promise<LocalSynchronizer> | A Promise containing a reference to the |
|---|
This guarantees that all of the listeners that the object registered with the underlying Store and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad method and the stopAutoSave method in succession. This method is asynchronous.
Example
This example creates a Store, associates a Persister object with it (that registers a TablesListener with the underlying Store), and then destroys it again, removing the listener.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
await persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus method lets you find out if the Persister is currently in the process of loading or saving content.
getStatus(): StatusIt can only be doing one or the other (or neither) at any given time. The Status enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister and queries its status.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<LocalSynchronizer>| Type | Description | |
|---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
| returns | Promise<LocalSynchronizer> | A reference to the |
For example, a database Persister may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister.
Example
This example creates a custom Persister object against a newly-created Store and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createStore} from 'tinybase';
import {createCustomPersister} from 'tinybase/persisters';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
() => checkRemoteSystemIsReady(),
() => sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
startAutoPersisting
The startAutoPersist method is a convenience method that starts both automatic loading and saving of the Store data.
startAutoPersisting(
initialContent?: Content | () => Content,
startSaveFirst?: boolean,
): Promise<LocalSynchronizer>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
startSaveFirst? | boolean | Whether to start saving before loading, defaulting to false. |
| returns | Promise<LocalSynchronizer> | A Promise containing a reference to the |
This simply runs the startAutoLoad and startAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
This method can take initialContent to pass to the startAutoLoad method. See its documentation for more details.
If for some reason you want to start saving before you start loading, pass true as the second parameter.
Example
and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
persister.destroy();
Since
v6.1.0
stopAutoPersisting
The stopAutoPersist method is a convenience method that stops both automatic loading and saving of the Store data.
stopAutoPersisting(stopSaveFirst?: boolean): Promise<LocalSynchronizer>| Type | Description | |
|---|---|---|
stopSaveFirst? | boolean | Whether to stop saving before loading, defaulting to false. |
| returns | Promise<LocalSynchronizer> | A Promise containing a reference to the |
This simply runs the stopAutoLoad and stopAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
If for some reason you want to stop saving before you stop loading, pass true as the second parameter.
Example
automatically loading and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoPersisting();
console.log(persister.isAutoSaving());
// -> false
console.log(persister.isAutoLoading());
// -> false
persister.destroy();
Since
v6.1.0
Load methods
isAutoLoading
The isAutoLoading method lets you find out if the Persister is currently automatically loading its content.
isAutoLoading(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoLoading.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once.
load(initialContent?: Content | () => Content): Promise<LocalSynchronizer>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<LocalSynchronizer> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load method is called, data has previously been persisted and instead, that is loaded.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once, and then continuously.
startAutoLoad(initialContent?: Content | () => Content): Promise<LocalSynchronizer>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<LocalSynchronizer> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method first runs a single call to the load method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store.
This method is asynchronous because it starts by making a single call to the asynchronous load method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store, and loads data into it from the browser's session storage, which at first is empty (so the initialTables parameter is used). Subsequent changes to the underlying storage are then reflected in the Store (in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad method stops the automatic loading of data from storage previously started with the startAutoLoad method.
stopAutoLoad(): Promise<LocalSynchronizer>| returns | Promise<LocalSynchronizer> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically load, this method has no effect. This method is asynchronous.
Example
This example creates an empty Store, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
isAutoSaving
The isAutoSaving method lets you find out if the Persister is currently automatically saving its content.
isAutoSaving(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoSaving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save method takes data from the Store with which the Persister is associated and persists it into storage, once.
save(): Promise<LocalSynchronizer>| returns | Promise<LocalSynchronizer> | A Promise containing a reference to the |
|---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save method takes data from the Store with which the Persister is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<LocalSynchronizer>| returns | Promise<LocalSynchronizer> | A Promise containing a reference to the |
|---|
This method first runs a single call to the save method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave method stops the automatic save of data to storage previously started with the startAutoSave method.
stopAutoSave(): Promise<LocalSynchronizer>| returns | Promise<LocalSynchronizer> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically save, this method has no effect. This method is asynchronous.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
await persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Synchronization methods
getSynchronizerStats
The getSynchronizerStats method provides a set of statistics about the Synchronizer, and is used for debugging purposes.
getSynchronizerStats(): SynchronizerStats| returns | SynchronizerStats | A |
|---|
The SynchronizerStats object contains a count of the number of times the Synchronizer has sent and received messages.
The method is intended to be used during development to ensure your synchronization layer is acting as expected, for example.
Example
This example gets the send and receive statistics of two active Synchronizer instances.
import {createMergeableStore} from 'tinybase';
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
const store1 = createMergeableStore();
const store2 = createMergeableStore();
const synchronizer1 = createLocalSynchronizer(store1);
const synchronizer2 = createLocalSynchronizer(store2);
await synchronizer1.startSync();
await synchronizer2.startSync();
store1.setTables({pets: {fido: {species: 'dog'}}});
// ...
store2.setRow('pets', 'felix', {species: 'cat'});
// ...
console.log(synchronizer1.getSynchronizerStats());
// -> {receives: 4, sends: 5}
console.log(synchronizer2.getSynchronizerStats());
// -> {receives: 5, sends: 4}
await synchronizer1.destroy();
await synchronizer2.destroy();
Since
v5.0.0
startSync
The startSync method is used to start the process of synchronization between this instance and another matching Synchronizer.
startSync(initialContent?: Content): Promise<LocalSynchronizer>| Type | Description | |
|---|---|---|
initialContent? | Content | An optional |
| returns | Promise<LocalSynchronizer> | A Promise containing a reference to the |
The underlying implementation of a Synchronizer is shared with the Persister framework, and so this startSync method is equivalent to starting both auto-loading (listening to sync messages from other active Synchronizer instances) and auto-saving (sending sync messages to it).
This method is asynchronous so you should you await calls to this method or handle the return type natively as a Promise.
Examples
This example creates two empty MergeableStore objects, creates a LocalSynchronizer for each, and starts synchronizing them. A change in one becomes evident in the other.
import {createMergeableStore} from 'tinybase';
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
const store1 = createMergeableStore();
const store2 = createMergeableStore();
const synchronizer1 = createLocalSynchronizer(store1);
const synchronizer2 = createLocalSynchronizer(store2);
await synchronizer1.startSync();
await synchronizer2.startSync();
store1.setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}}}
store2.setRow('pets', 'felix', {species: 'cat'});
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
await synchronizer1.destroy();
await synchronizer2.destroy();
This example creates two empty MergeableStore objects, creates a LocalSynchronizer for each, and starts synchronizing them with default content. The default content from the first Synchronizer's startSync method ends up populated in both MergeableStore instances: by the time the second started, the first was available to synchronize with and its default was not needed.
import {createMergeableStore} from 'tinybase';
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
const store1 = createMergeableStore();
const store2 = createMergeableStore();
const synchronizer1 = createLocalSynchronizer(store1);
const synchronizer2 = createLocalSynchronizer(store2);
await synchronizer1.startSync([{pets: {fido: {species: 'dog'}}}, {}]);
await synchronizer2.startSync([{pets: {felix: {species: 'cat'}}}, {}]);
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog'}}}
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}}}
await synchronizer1.destroy();
await synchronizer2.destroy();
Since
v5.0.0
stopSync
The stopSync method is used to stop the process of synchronization between this instance and another matching Synchronizer.
stopSync(): Promise<LocalSynchronizer>| returns | Promise<LocalSynchronizer> | A Promise containing a reference to the |
|---|
The underlying implementation of a Synchronizer is shared with the Persister framework, and so this startSync method is equivalent to stopping both auto-loading (listening to sync messages from other active Synchronizer instances) and auto-saving (sending sync messages to them).
This method is asynchronous.
Example
This example creates two empty MergeableStore objects, creates a LocalSynchronizer for each, and starts - then stops - synchronizing them. Subsequent changes are not merged.
import {createMergeableStore} from 'tinybase';
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
const store1 = createMergeableStore();
const synchronizer1 = createLocalSynchronizer(store1);
await synchronizer1.startSync();
const store2 = createMergeableStore();
const synchronizer2 = createLocalSynchronizer(store2);
await synchronizer2.startSync();
store1.setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog'}}}
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}}}
await synchronizer1.stopSync();
await synchronizer2.stopSync();
store1.setCell('pets', 'fido', 'color', 'brown');
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog', color: 'brown'}}}
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}}}
await synchronizer1.destroy();
await synchronizer2.destroy();
Since
v5.0.0
Development methods
getStats
The getStats method provides a set of statistics about the Persister, and is used for debugging purposes.
getStats(): PersisterStats| returns | PersisterStats | A |
|---|
The PersisterStats object contains a count of the number of times the Persister has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister object. Remember that the startAutoLoad method invokes an explicit load when it starts, and the startAutoSave method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store and to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
createLocalSynchronizer
The createLocalSynchronizer function creates a LocalSynchronizer object that can synchronize MergeableStore data to and from other MergeableStore instances on the same local machine.
createLocalSynchronizer(
store: MergeableStore,
onSend?: Send,
onReceive?: Receive,
onIgnoredError?: (error: any) => void,
): LocalSynchronizer| Type | Description | |
|---|---|---|
store | MergeableStore | The |
onSend? | Send | An optional handler for the messages that this |
onReceive? | Receive | An optional handler for the messages that this |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | LocalSynchronizer | A reference to the new |
This is something of a showcase Synchronizer, rather than something you would use in a production environment. If you do need to synchronize two in-memory MergeableStore instances, you may prefer to use the merge function on either one of them instead of going to the effort of setting up this Synchronizer.
As well as providing a reference to the MergeableStore to persist, a final set of optional handlers can be provided to help debug sends, receives, and errors respectively.
Example
This example creates two LocalSynchronizer objects to synchronize one MergeableStore to another.
import {createMergeableStore} from 'tinybase';
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
const store1 = createMergeableStore();
const store2 = createMergeableStore();
const synchronizer1 = createLocalSynchronizer(store1);
const synchronizer2 = createLocalSynchronizer(store2);
await synchronizer1.startSync();
await synchronizer2.startSync();
store1.setTables({pets: {fido: {species: 'dog'}}});
store2.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
await synchronizer1.destroy();
await synchronizer2.destroy();
Since
v5.0.0
synchronizer-ws-client
The synchronizer-ws module of the TinyBase project lets you synchronize MergeableStore data to and from other MergeableStore instances via WebSockets facilitated by a server.
See also
- Synchronization guide
- Todo App v6 (collaboration) demo
Since
v5.0.0
Interfaces
WsSynchronizer
The WsSynchronizer interface represents a Synchronizer that lets you synchronize MergeableStore data to and from other MergeableStore instances via WebSockets facilitated by a server.
You should use the createWsSynchronizer function to create a WsSynchronizer object.
It is a minor extension to the Synchronizer interface and simply provides an extra getWebSocket method for accessing a reference to the WebSocket being used.
Since
v5.0.0
Getter methods
getStore
The getStore method returns a reference to the underlying Store or MergeableStore that is backing this Persister object.
getStore(): MergeableStore| returns | MergeableStore | A reference to the |
|---|
Example
This example creates a Persister object against a newly-created Store and then gets its reference in order to update its data.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getWebSocket
The getWebSocket method returns a reference to the WebSocket being used for synchronization.
getWebSocket(): WebSocketType| returns | WebSocketType | The WebSocket reference. |
|---|
Example
This example creates a server and WsSynchronizer object for a newly-created MergeableStore and then gets the WebSocket reference back out again.
import {createMergeableStore} from 'tinybase';
import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';
import {createWsServer} from 'tinybase/synchronizers/synchronizer-ws-server';
import {WebSocket, WebSocketServer} from 'ws';
const server = createWsServer(new WebSocketServer({port: 8046}));
const store = createMergeableStore();
const webSocket = new WebSocket('ws://localhost:8046');
const synchronizer = await createWsSynchronizer(store, webSocket);
console.log(synchronizer.getWebSocket() == webSocket);
// -> true
await synchronizer.destroy();
await server.destroy();
Since
v5.0.0
Listener methods
addStatusListener
The addStatusListener method registers a listener function with the Persister that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<MergeableStoreOnly>): string| Type | Description | |
|---|---|---|
listener | StatusListener<MergeableStoreOnly> | The function that will be called whenever the |
| returns | string | A unique |
The provided listener is a StatusListener function, and will be called with a reference to the Persister and the new Status: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener method removes a listener that was previously added to the Persister.
delListener(listenerId: string): this| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | this | A reference to the |
Use the Id returned by whichever method was used to add the listener. Note that the Persister may re-use this Id for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
destroy
The destroy method should be called when this Persister object is no longer used.
destroy(): Promise<WsSynchronizer<WebSocketType>>| returns | Promise<WsSynchronizer<WebSocketType>> | A Promise containing a reference to the |
|---|
This guarantees that all of the listeners that the object registered with the underlying Store and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad method and the stopAutoSave method in succession. This method is asynchronous.
Example
This example creates a Store, associates a Persister object with it (that registers a TablesListener with the underlying Store), and then destroys it again, removing the listener.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
await persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus method lets you find out if the Persister is currently in the process of loading or saving content.
getStatus(): StatusIt can only be doing one or the other (or neither) at any given time. The Status enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister and queries its status.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<WsSynchronizer<WebSocketType>>| Type | Description | |
|---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
| returns | Promise<WsSynchronizer<WebSocketType>> | A reference to the |
For example, a database Persister may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister.
Example
This example creates a custom Persister object against a newly-created Store and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createStore} from 'tinybase';
import {createCustomPersister} from 'tinybase/persisters';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
() => checkRemoteSystemIsReady(),
() => sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
startAutoPersisting
The startAutoPersist method is a convenience method that starts both automatic loading and saving of the Store data.
startAutoPersisting(
initialContent?: Content | () => Content,
startSaveFirst?: boolean,
): Promise<WsSynchronizer<WebSocketType>>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
startSaveFirst? | boolean | Whether to start saving before loading, defaulting to false. |
| returns | Promise<WsSynchronizer<WebSocketType>> | A Promise containing a reference to the |
This simply runs the startAutoLoad and startAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
This method can take initialContent to pass to the startAutoLoad method. See its documentation for more details.
If for some reason you want to start saving before you start loading, pass true as the second parameter.
Example
and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
persister.destroy();
Since
v6.1.0
stopAutoPersisting
The stopAutoPersist method is a convenience method that stops both automatic loading and saving of the Store data.
stopAutoPersisting(stopSaveFirst?: boolean): Promise<WsSynchronizer<WebSocketType>>| Type | Description | |
|---|---|---|
stopSaveFirst? | boolean | Whether to stop saving before loading, defaulting to false. |
| returns | Promise<WsSynchronizer<WebSocketType>> | A Promise containing a reference to the |
This simply runs the stopAutoLoad and stopAutoSave methods in sequence, and returns a Promise that resolves when both have completed.
If for some reason you want to stop saving before you stop loading, pass true as the second parameter.
Example
automatically loading and saving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoPersisting();
console.log(persister.isAutoSaving());
// -> true
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoPersisting();
console.log(persister.isAutoSaving());
// -> false
console.log(persister.isAutoLoading());
// -> false
persister.destroy();
Since
v6.1.0
Load methods
isAutoLoading
The isAutoLoading method lets you find out if the Persister is currently automatically loading its content.
isAutoLoading(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoLoading.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once.
load(initialContent?: Content | () => Content): Promise<WsSynchronizer<WebSocketType>>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<WsSynchronizer<WebSocketType>> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load method is called, data has previously been persisted and instead, that is loaded.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once, and then continuously.
startAutoLoad(initialContent?: Content | () => Content): Promise<WsSynchronizer<WebSocketType>>| Type | Description | |
|---|---|---|
initialContent? | Content | () => Content | An optional |
| returns | Promise<WsSynchronizer<WebSocketType>> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store will be if there is nothing currently persisted or if the load fails (for example when the Persister is remote and the environment is offline). This allows you to fallback or instantiate a Store whether it's loading from previously persisted storage or being run for the first time. Since v5.4.2, this parameter can also be a function that returns the content.
This method first runs a single call to the load method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store.
This method is asynchronous because it starts by making a single call to the asynchronous load method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store, and loads data into it from the browser's session storage, which at first is empty (so the initialTables parameter is used). Subsequent changes to the underlying storage are then reflected in the Store (in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad method stops the automatic loading of data from storage previously started with the startAutoLoad method.
stopAutoLoad(): Promise<WsSynchronizer<WebSocketType>>| returns | Promise<WsSynchronizer<WebSocketType>> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically load, this method has no effect. This method is asynchronous.
Example
This example creates an empty Store, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
await persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
isAutoSaving
The isAutoSaving method lets you find out if the Persister is currently automatically saving its content.
isAutoSaving(): boolean| returns | boolean | A boolean indicating whether the |
|---|
Example
This example creates a Persister and queries whether it is autoSaving.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save method takes data from the Store with which the Persister is associated and persists it into storage, once.
save(): Promise<WsSynchronizer<WebSocketType>>| returns | Promise<WsSynchronizer<WebSocketType>> | A Promise containing a reference to the |
|---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save method takes data from the Store with which the Persister is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<WsSynchronizer<WebSocketType>>| returns | Promise<WsSynchronizer<WebSocketType>> | A Promise containing a reference to the |
|---|
This method first runs a single call to the save method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave method stops the automatic save of data to storage previously started with the startAutoSave method.
stopAutoSave(): Promise<WsSynchronizer<WebSocketType>>| returns | Promise<WsSynchronizer<WebSocketType>> | A Promise containing a reference to the |
|---|
If the Persister is not currently set to automatically save, this method has no effect. This method is asynchronous.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
await persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Synchronization methods
getSynchronizerStats
The getSynchronizerStats method provides a set of statistics about the Synchronizer, and is used for debugging purposes.
getSynchronizerStats(): SynchronizerStats| returns | SynchronizerStats | A |
|---|
The SynchronizerStats object contains a count of the number of times the Synchronizer has sent and received messages.
The method is intended to be used during development to ensure your synchronization layer is acting as expected, for example.
Example
This example gets the send and receive statistics of two active Synchronizer instances.
import {createMergeableStore} from 'tinybase';
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
const store1 = createMergeableStore();
const store2 = createMergeableStore();
const synchronizer1 = createLocalSynchronizer(store1);
const synchronizer2 = createLocalSynchronizer(store2);
await synchronizer1.startSync();
await synchronizer2.startSync();
store1.setTables({pets: {fido: {species: 'dog'}}});
// ...
store2.setRow('pets', 'felix', {species: 'cat'});
// ...
console.log(synchronizer1.getSynchronizerStats());
// -> {receives: 4, sends: 5}
console.log(synchronizer2.getSynchronizerStats());
// -> {receives: 5, sends: 4}
await synchronizer1.destroy();
await synchronizer2.destroy();
Since
v5.0.0
startSync
The startSync method is used to start the process of synchronization between this instance and another matching Synchronizer.
startSync(initialContent?: Content): Promise<WsSynchronizer<WebSocketType>>| Type | Description | |
|---|---|---|
initialContent? | Content | An optional |
| returns | Promise<WsSynchronizer<WebSocketType>> | A Promise containing a reference to the |
The underlying implementation of a Synchronizer is shared with the Persister framework, and so this startSync method is equivalent to starting both auto-loading (listening to sync messages from other active Synchronizer instances) and auto-saving (sending sync messages to it).
This method is asynchronous so you should you await calls to this method or handle the return type natively as a Promise.
Examples
This example creates two empty MergeableStore objects, creates a LocalSynchronizer for each, and starts synchronizing them. A change in one becomes evident in the other.
import {createMergeableStore} from 'tinybase';
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
const store1 = createMergeableStore();
const store2 = createMergeableStore();
const synchronizer1 = createLocalSynchronizer(store1);
const synchronizer2 = createLocalSynchronizer(store2);
await synchronizer1.startSync();
await synchronizer2.startSync();
store1.setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}}}
store2.setRow('pets', 'felix', {species: 'cat'});
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
await synchronizer1.destroy();
await synchronizer2.destroy();
This example creates two empty MergeableStore objects, creates a LocalSynchronizer for each, and starts synchronizing them with default content. The default content from the first Synchronizer's startSync method ends up populated in both MergeableStore instances: by the time the second started, the first was available to synchronize with and its default was not needed.
import {createMergeableStore} from 'tinybase';
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
const store1 = createMergeableStore();
const store2 = createMergeableStore();
const synchronizer1 = createLocalSynchronizer(store1);
const synchronizer2 = createLocalSynchronizer(store2);
await synchronizer1.startSync([{pets: {fido: {species: 'dog'}}}, {}]);
await synchronizer2.startSync([{pets: {felix: {species: 'cat'}}}, {}]);
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog'}}}
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}}}
await synchronizer1.destroy();
await synchronizer2.destroy();
Since
v5.0.0
stopSync
The stopSync method is used to stop the process of synchronization between this instance and another matching Synchronizer.
stopSync(): Promise<WsSynchronizer<WebSocketType>>| returns | Promise<WsSynchronizer<WebSocketType>> | A Promise containing a reference to the |
|---|
The underlying implementation of a Synchronizer is shared with the Persister framework, and so this startSync method is equivalent to stopping both auto-loading (listening to sync messages from other active Synchronizer instances) and auto-saving (sending sync messages to them).
This method is asynchronous.
Example
This example creates two empty MergeableStore objects, creates a LocalSynchronizer for each, and starts - then stops - synchronizing them. Subsequent changes are not merged.
import {createMergeableStore} from 'tinybase';
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
const store1 = createMergeableStore();
const synchronizer1 = createLocalSynchronizer(store1);
await synchronizer1.startSync();
const store2 = createMergeableStore();
const synchronizer2 = createLocalSynchronizer(store2);
await synchronizer2.startSync();
store1.setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog'}}}
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}}}
await synchronizer1.stopSync();
await synchronizer2.stopSync();
store1.setCell('pets', 'fido', 'color', 'brown');
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog', color: 'brown'}}}
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}}}
await synchronizer1.destroy();
await synchronizer2.destroy();
Since
v5.0.0
Development methods
getStats
The getStats method provides a set of statistics about the Persister, and is used for debugging purposes.
getStats(): PersisterStats| returns | PersisterStats | A |
|---|
The PersisterStats object contains a count of the number of times the Persister has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister object. Remember that the startAutoLoad method invokes an explicit load when it starts, and the startAutoSave method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store and to the underlying storage.
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
await persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
createWsSynchronizer
The createWsSynchronizer function creates a WsSynchronizer object that can synchronize MergeableStore data to and from other MergeableStore instances via WebSockets facilitated by a WsServer.
createWsSynchronizer<WebSocketType>(
store: MergeableStore,
webSocket: WebSocketType,
requestTimeoutSeconds?: number,
onSend?: Send,
onReceive?: Receive,
onIgnoredError?: (error: any) => void,
): Promise<WsSynchronizer<WebSocketType>>| Type | Description | |
|---|---|---|
store | MergeableStore | The |
webSocket | WebSocketType | The WebSocket to send synchronization messages over. |
requestTimeoutSeconds? | number | An optional time in seconds that the |
onSend? | Send | An optional handler for the messages that this |
onReceive? | Receive | An optional handler for the messages that this |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | Promise<WsSynchronizer<WebSocketType>> | A reference to the new |
As well as providing a reference to the MergeableStore to persist, you must provide a configured WebSocket to send synchronization messages over.
Instead of the raw browser implementation of WebSocket, you may prefer to use the Reconnecting WebSocket wrapper so that if a client goes offline, it can easily re-establish a connection when it comes back online. Its API is compatible with this Synchronizer.
You can indicate how long the Synchronizer will wait for responses to message requests before timing out. A final set of optional handlers can be provided to help debug sends, receives, and errors respectively.
This method is asynchronous because it will await the websocket's connection to the server. You will need to await a call to this function or handle the return type natively as a Promise.
Example
This example creates two WsSynchronizer objects to synchronize one MergeableStore to another via a server.
import {createMergeableStore} from 'tinybase';
import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';
import {createWsServer} from 'tinybase/synchronizers/synchronizer-ws-server';
import {WebSocket, WebSocketServer} from 'ws';
const server = createWsServer(new WebSocketServer({port: 8047}));
const store1 = createMergeableStore();
const store2 = createMergeableStore();
const synchronizer1 = await createWsSynchronizer(
store1,
new WebSocket('ws://localhost:8047'),
);
const synchronizer2 = await createWsSynchronizer(
store2,
new WebSocket('ws://localhost:8047'),
);
await synchronizer1.startSync();
await synchronizer2.startSync();
store1.setTables({pets: {fido: {species: 'dog'}}});
store2.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
await synchronizer1.destroy();
await synchronizer2.destroy();
await server.destroy();
Since
v5.0.0
Type Aliases
WebSocketTypes
The WebSocketTypes type represents the valid types of WebSocket that can be used with the WsSynchronizer.
WebSocket | WsWebSocketThis includes the browser-native WebSocket type, as well as the WebSocket type from the well-known ws package (such that the Synchronizer can be used in a server environment).
Since
v5.0.0
synchronizer-ws-server
The synchronizer-ws-server module of the TinyBase project lets you create a server that facilitates synchronization between clients.
See also
- Synchronization guide
- Todo App v6 (collaboration) demo
Since
v5.0.0
Interfaces
WsServer
The WsServer interface represents an object that facilitates synchronization between clients that are using WsSynchronizer instances.
You should use the createWsServer function to create a WsServer object.
Since
v5.0.0
Getter methods
destroy
The destroy method provides a way to clean up the server at the end of its use.
destroy(): Promise<void>| returns | Promise<void> |
|---|
This closes the underlying WebSocketServer that was provided when the WsServer was created. This method is asynchronous.
Example
This example creates a WsServer and then destroys it again, closing the underlying WebSocketServer.
import {createWsServer} from 'tinybase/synchronizers/synchronizer-ws-server';
import {WebSocketServer} from 'ws';
const webSocketServer = new WebSocketServer({port: 8047});
webSocketServer.on('close', () => {
console.log('WebSocketServer closed');
});
const server = createWsServer(webSocketServer);
await server.destroy();
// ...
// -> 'WebSocketServer closed'
Since
v5.0.0
getClientIds
The getClientIds method method returns the active clients that the WsServer is handling for a given path.
getClientIds(pathId: string): Ids| Type | Description | |
|---|---|---|
pathId | string | The path for which to return the list of active clients. |
| returns | Ids | An array of the clients connected to the given path. |
Example
This example creates a WsServer, sets some clients up to connect to it, and then gets the number of clients on the given paths. (The client Ids themselves are unique, based on the sec-websocket-key header.)
import {createMergeableStore} from 'tinybase';
import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';
import {createWsServer} from 'tinybase/synchronizers/synchronizer-ws-server';
import {WebSocket, WebSocketServer} from 'ws';
const server = createWsServer(new WebSocketServer({port: 8047}));
const synchronizer1 = await createWsSynchronizer(
createMergeableStore(),
new WebSocket('ws://localhost:8047/roomA'),
);
const synchronizer2 = await createWsSynchronizer(
createMergeableStore(),
new WebSocket('ws://localhost:8047/roomA'),
);
const synchronizer3 = await createWsSynchronizer(
createMergeableStore(),
new WebSocket('ws://localhost:8047/roomB'),
);
console.log(server.getClientIds('roomA').length);
// -> 2
console.log(server.getClientIds('roomB').length);
// -> 1
await synchronizer3.destroy();
// ...
console.log(server.getClientIds('roomB').length);
// -> 0
await synchronizer1.destroy();
await synchronizer2.destroy();
await server.destroy();
Since
v5.0.0
getPathIds
The getPathIds method returns the active paths that the WsServer is handling.
getPathIds(): Ids| returns | Ids | An array of the paths that have clients connected to them. |
|---|
These will be all the paths that have at least one active client connected to them.
Example
This example creates a WsServer, sets some clients up to connect to it, and then enumerates the paths being used.
import {createMergeableStore} from 'tinybase';
import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';
import {createWsServer} from 'tinybase/synchronizers/synchronizer-ws-server';
import {WebSocket, WebSocketServer} from 'ws';
const server = createWsServer(new WebSocketServer({port: 8047}));
console.log(server.getPathIds());
// -> []
const synchronizer1 = await createWsSynchronizer(
createMergeableStore(),
new WebSocket('ws://localhost:8047/roomA'),
);
const synchronizer2 = await createWsSynchronizer(
createMergeableStore(),
new WebSocket('ws://localhost:8047/roomA'),
);
const synchronizer3 = await createWsSynchronizer(
createMergeableStore(),
new WebSocket('ws://localhost:8047/roomB'),
);
console.log(server.getPathIds());
// -> ['roomA', 'roomB']
await synchronizer3.destroy();
// ...
console.log(server.getPathIds());
// -> ['roomA']
await synchronizer1.destroy();
await synchronizer2.destroy();
await server.destroy();
Since
v5.0.0
getWebSocketServer
The getWebSocketServer method returns a reference to the WebSocketServer being used for this WsServer.
getWebSocketServer(): WebSocketServer| returns | WebSocketServer | The WebSocketServer reference. |
|---|
Example
This example creates a WsServer and then gets the WebSocketServer reference back out again.
import {createWsServer} from 'tinybase/synchronizers/synchronizer-ws-server';
import {WebSocketServer} from 'ws';
const webSocketServer = new WebSocketServer({port: 8047});
const server = createWsServer(webSocketServer);
console.log(server.getWebSocketServer() == webSocketServer);
// -> true
await server.destroy();
Since
v5.0.0
Listener methods
addClientIdsListener
The addClientIdsListener method registers a listener function with the WsServer that will be called whenever there is a change in the clients connected to a path that a WsServer is handling.
addClientIdsListener(
pathId: IdOrNull,
listener: ClientIdsListener,
): string| Type | Description | |
|---|---|---|
pathId | IdOrNull | The path to listen to, or |
listener | ClientIdsListener | The function that will be called whenever the client |
| returns | string | A unique |
The provided listener is a ClientIdsListener function, and will be called with a reference to the WsServer, the Id of the path that the client joined or left, and a callback you can use to get information about the change.
You can either listen to a single path (by specifying its Id as the method's first parameter) or changes to any path (by providing a null wildcard).
Examples
This example creates a WsServer, and listens to changes to the clients connecting to and disconnecting from a specific path.
import {createMergeableStore} from 'tinybase';
import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';
import {createWsServer} from 'tinybase/synchronizers/synchronizer-ws-server';
import {WebSocket, WebSocketServer} from 'ws';
const server = createWsServer(new WebSocketServer({port: 8047}));
const listenerId = server.addClientIdsListener(
'roomA',
(server, pathId) => {
console.log(
`${server.getClientIds(pathId).length} client(s) in roomA`,
);
},
);
const synchronizer1 = await createWsSynchronizer(
createMergeableStore(),
new WebSocket('ws://localhost:8047/roomA'),
);
// -> '1 client(s) in roomA'
const synchronizer2 = await createWsSynchronizer(
createMergeableStore(),
new WebSocket('ws://localhost:8047/roomB'),
);
// The listener is not called.
await synchronizer1.destroy();
// ...
// -> '0 client(s) in roomA'
await synchronizer2.destroy();
server.delListener(listenerId);
await server.destroy();
This example creates a WsServer, and listens to changes to the clients connecting to and disconnecting from any path.
import {createMergeableStore} from 'tinybase';
import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';
import {createWsServer} from 'tinybase/synchronizers/synchronizer-ws-server';
import {WebSocket, WebSocketServer} from 'ws';
const server = createWsServer(new WebSocketServer({port: 8047}));
const listenerId = server.addClientIdsListener(null, (server, pathId) => {
console.log(
`${server.getClientIds(pathId).length} client(s) in ${pathId}`,
);
});
const synchronizer1 = await createWsSynchronizer(
createMergeableStore(),
new WebSocket('ws://localhost:8047/roomA'),
);
// -> '1 client(s) in roomA'
const synchronizer2 = await createWsSynchronizer(
createMergeableStore(),
new WebSocket('ws://localhost:8047/roomB'),
);
// -> '1 client(s) in roomB'
await synchronizer1.destroy();
// ...
// -> '0 client(s) in roomA'
await synchronizer2.destroy();
// ...
// -> '0 client(s) in roomB'
server.delListener(listenerId);
await server.destroy();
Since
v5.0.0
addPathIdsListener
The addPathIdsListener method registers a listener function with the WsServer that will be called whenever there is a change in the active paths that a WsServer is handling.
addPathIdsListener(listener: PathIdsListener): string| Type | Description | |
|---|---|---|
listener | PathIdsListener | The function that will be called whenever the path |
| returns | string | A unique |
The provided listener is a PathIdsListener function, and will be called with a reference to the WsServer and a callback you can use to get information about the change.
Example
This example creates a WsServer, and listens to changes to the active paths when clients connect to and disconnect from it.
import {createMergeableStore} from 'tinybase';
import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';
import {createWsServer} from 'tinybase/synchronizers/synchronizer-ws-server';
import {WebSocket, WebSocketServer} from 'ws';
const server = createWsServer(new WebSocketServer({port: 8047}));
const listenerId = server.addPathIdsListener(
(server, pathId, addedOrRemoved) => {
console.log(pathId + (addedOrRemoved == 1 ? ' added' : ' removed'));
console.log(server.getPathIds());
},
);
const synchronizer1 = await createWsSynchronizer(
createMergeableStore(),
new WebSocket('ws://localhost:8047/roomA'),
);
// -> 'roomA added'
// -> ['roomA']
const synchronizer2 = await createWsSynchronizer(
createMergeableStore(),
new WebSocket('ws://localhost:8047/roomB'),
);
// -> 'roomB added'
// -> ['roomA', 'roomB']
await synchronizer1.destroy();
// ...
// -> 'roomA removed'
// -> ['roomB']
await synchronizer2.destroy();
// ...
// -> 'roomB removed'
// -> []
server.delListener(listenerId);
await server.destroy();
Since
v5.0.0
delListener
The delListener method removes a listener that was previously added to the WsServer.
delListener(listenerId: string): WsServer| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | WsServer | A reference to the |
Use the Id returned by whichever method was used to add the listener. Note that the WsServer may re-use this Id for future listeners added to it.
Example
This example registers a listener to a WsServer and then removes it.
import {createMergeableStore} from 'tinybase';
import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';
import {createWsServer} from 'tinybase/synchronizers/synchronizer-ws-server';
import {WebSocket, WebSocketServer} from 'ws';
const server = createWsServer(new WebSocketServer({port: 8047}));
const listenerId = server.addPathIdsListener(() => {
console.log('Paths changed');
});
const synchronizer = await createWsSynchronizer(
createMergeableStore(),
new WebSocket('ws://localhost:8047/roomA'),
);
// -> 'Paths changed'
server.delListener(listenerId);
await synchronizer.destroy();
// -> undefined
// The listener is not called.
await server.destroy();
Since
v5.0.0
Development methods
getStats
The getStats method provides a set of statistics about the WsServer, and is used for debugging purposes.
getStats(): WsServerStats| returns | WsServerStats | A |
|---|
The WsServerStats object contains the number of paths and clients that are active on the WsServer and is intended to be used during development.
Example
This example creates a WsServer that facilitates some synchronization, demonstrating the statistics of the paths and clients handled as a result.
import {createMergeableStore} from 'tinybase';
import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';
import {createWsServer} from 'tinybase/synchronizers/synchronizer-ws-server';
import {WebSocket, WebSocketServer} from 'ws';
const server = createWsServer(new WebSocketServer({port: 8047}));
const synchronizer1 = await createWsSynchronizer(
createMergeableStore(),
new WebSocket('ws://localhost:8047'),
);
const synchronizer2 = await createWsSynchronizer(
createMergeableStore(),
new WebSocket('ws://localhost:8047'),
);
console.log(server.getStats());
// -> {paths: 1, clients: 2}
await synchronizer1.destroy();
await synchronizer2.destroy();
await server.destroy();
Since
v5.0.0
Functions
createWsServer
The createWsServer function creates a WsServer that facilitates synchronization between clients that are using WsSynchronizer instances.
createWsServer<PathPersister>(
webSocketServer: WebSocketServer,
createPersisterForPath?: (pathId: string) => undefined | PathPersister | [PathPersister, (store: MergeableStore) => void] | Promise<PathPersister> | Promise<[PathPersister, (store: MergeableStore) => void]>,
onIgnoredError?: (error: any) => void,
): WsServer| Type | Description | |
|---|---|---|
webSocketServer | WebSocketServer | A WebSocketServer object from your server environment. |
createPersisterForPath? | (pathId: string) => undefined | PathPersister | [PathPersister, (store: MergeableStore) => void] | Promise<PathPersister> | Promise<[PathPersister, (store: MergeableStore) => void]> | An optional function that will create a |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the server would otherwise ignore when trying to sync data. This is suitable for debugging issues in a development environment. |
| returns | WsServer | A reference to the new |
This should be run in a server environment, and you must pass in a configured WebSocketServer object in order to create it.
If you want your server to persist data itself, you can use the optional second parameter of this function, which allows you to create a Persister for a new path - whenever a new path is accessed by a client. This Persister will only exist when there are active clients on that particular path. The creation callback can be asynchronous.
You are responsible for creating a MergeableStore to pass to this Persister, but starting and stopping its automatic saving and loading is taken care of by the WsServer. As a result, the server MergeableStore will be kept in sync with the clients on that path, and in turn with whatever persistence layer you have configured. See the example below.
It is not safe to add or manipulate data in the MergeableStore during the createPersisterForPath function, since changes will probably be overwritten when the Persister starts. If you wish to modify data - or upgrade a schema, for example - you can have that function instead return an array containing the Persister and a callback that takes the MergeableStore. That callback will get called after the Persister has started, and is an appropriate place to manipulate data in a way that will be transmitted to clients. Again, see the example below.
Examples
This example creates a WsServer that synchronizes two clients on a shared path.
import {createMergeableStore} from 'tinybase';
import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';
import {createWsServer} from 'tinybase/synchronizers/synchronizer-ws-server';
import {WebSocket, WebSocketServer} from 'ws';
// Server
const server = createWsServer(new WebSocketServer({port: 8047}));
// Client 1
const clientStore1 = createMergeableStore();
clientStore1.setCell('pets', 'fido', 'species', 'dog');
const synchronizer1 = await createWsSynchronizer(
clientStore1,
new WebSocket('ws://localhost:8047/petShop'),
);
await synchronizer1.startSync();
// ...
// Client 2
const clientStore2 = createMergeableStore();
clientStore2.setCell('pets', 'felix', 'species', 'cat');
const synchronizer2 = await createWsSynchronizer(
clientStore2,
new WebSocket('ws://localhost:8047/petShop'),
);
await synchronizer2.startSync();
// ...
console.log(clientStore1.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
console.log(clientStore2.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
await synchronizer1.destroy();
await synchronizer2.destroy();
await server.destroy();
This longer example creates a WsServer that persists a MergeableStore to file that is synchronized with two clients on a shared path. Later, when a third client connects, it picks up the data the previous two were using.
import {rmSync} from 'fs';
import {createMergeableStore} from 'tinybase';
import {createFilePersister} from 'tinybase/persisters/persister-file';
import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';
import {createWsServer} from 'tinybase/synchronizers/synchronizer-ws-server';
import {WebSocket, WebSocketServer} from 'ws';
// Server
const server = createWsServer(
new WebSocketServer({port: 8047}),
(pathId) =>
createFilePersister(
createMergeableStore(),
'./tmp/' + pathId.replace(/[^a-zA-Z0-9]/g, '-') + '.json',
),
);
// Client 1
const clientStore1 = createMergeableStore();
clientStore1.setCell('pets', 'fido', 'species', 'dog');
const synchronizer1 = await createWsSynchronizer(
clientStore1,
new WebSocket('ws://localhost:8047/petShop'),
);
await synchronizer1.startSync();
// ...
// Client 2
const clientStore2 = createMergeableStore();
clientStore2.setCell('pets', 'felix', 'species', 'cat');
const synchronizer2 = await createWsSynchronizer(
clientStore2,
new WebSocket('ws://localhost:8047/petShop'),
);
await synchronizer2.startSync();
// ...
console.log(clientStore1.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
console.log(clientStore2.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
await synchronizer1.destroy();
await synchronizer2.destroy();
// ...
// Client 3 connects later
const clientStore3 = createMergeableStore();
const synchronizer3 = await createWsSynchronizer(
clientStore3,
new WebSocket('ws://localhost:8047/petShop'),
);
await synchronizer3.startSync();
// ...
console.log(clientStore3.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
await synchronizer3.destroy();
await server.destroy();
// Remove file for the purposes of this demo.
rmSync('./tmp/petShop.json');
This example creates a WsServer that persists a MergeableStore to file that is synchronized with two clients on a shared path, but also which updates its data once synchronization has started.
import {rmSync} from 'fs';
import {createMergeableStore} from 'tinybase';
import {createFilePersister} from 'tinybase/persisters/persister-file';
import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';
import {createWsServer} from 'tinybase/synchronizers/synchronizer-ws-server';
import {WebSocket, WebSocketServer} from 'ws';
// Server
const server = createWsServer(
new WebSocketServer({port: 8047}),
(pathId) => [
createFilePersister(
createMergeableStore(),
'./tmp/' + pathId.replace(/[^a-zA-Z0-9]/g, '-') + '.json',
),
(store) => store.setValue('pathId', pathId),
],
);
const clientStore = createMergeableStore();
clientStore.setCell('pets', 'fido', 'species', 'dog');
const synchronizer = await createWsSynchronizer(
clientStore,
new WebSocket('ws://localhost:8047/petShop'),
);
await synchronizer.startSync();
// ...
console.log(clientStore.getContent());
// -> [{pets: {fido: {species: 'dog'}}}, {"pathId": "petShop"}]
await synchronizer.destroy();
await server.destroy();
// Remove file for the purposes of this demo.
rmSync('./tmp/petShop.json');
This example creates a WsServer with a custom listener that displays information about the address of the client that connects to it.
import {createMergeableStore} from 'tinybase';
import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';
import {createWsServer} from 'tinybase/synchronizers/synchronizer-ws-server';
import {WebSocket, WebSocketServer} from 'ws';
// On the server:
const webSocketServer = new WebSocketServer({port: 8047});
webSocketServer.on('connection', (_, request) => {
if (request.headers.connection == 'Upgrade') {
console.log('Local client connected');
}
});
const server = createWsServer(webSocketServer);
// On a client:
const synchronizer = await createWsSynchronizer(
createMergeableStore(),
new WebSocket('ws://localhost:8047'),
);
// -> 'Local client connected'
await synchronizer.destroy();
await server.destroy();
Since
v5.0.0
Type Aliases
Listener type aliases
ClientIdsListener
The ClientIdsListener type describes a function that is used to listen to clients joining and leaving the active paths that a WsServer is handling.
(
wsServer: WsServer,
pathId: Id,
clientId: Id,
addedOrRemoved: IdAddedOrRemoved,
): void| Type | Description | |
|---|---|---|
wsServer | WsServer | A reference to the |
pathId | Id | The path that the client joined or left. |
clientId | Id | The |
addedOrRemoved | IdAddedOrRemoved | Whether the client was added ( |
| returns | void | This has no return value. |
A WsServer listens to any path, allowing an app to have the concept of distinct 'rooms' that only certain clients are participating in. As soon as a new client connects to a path, this listener will be called with the Id of the path, the Id of the new client, and an addedOrRemoved value of 1.
When the client disconnects from a path, it will be called again with the Id of the path, the Id of the leaving client, and an addedOrRemoved value of -1.
A ClientIdsListener is provided when using the addClientIdsListener method. See that method for specific examples.
Since
v5.0.3
PathIdsListener
The PathIdsListener type describes a function that is used to listen to changes of active paths that a WsServer is handling.
(
wsServer: WsServer,
pathId: Id,
addedOrRemoved: IdAddedOrRemoved,
): void| Type | Description | |
|---|---|---|
wsServer | WsServer | A reference to the |
pathId | Id | The |
addedOrRemoved | IdAddedOrRemoved | Whether the path was added ( |
| returns | void | This has no return value. |
A WsServer listens to any path, allowing an app to have the concept of distinct 'rooms' that only certain clients are participating in. As soon as a single client connects to a new path, this listener will be called with the Id of the new path and an addedOrRemoved value of 1.
When the final client disconnects from a path, it will be called again with the Id of the deactivated path and an addedOrRemoved value of -1.
A PathIdsListener is provided when using the addPathIdsListener method. See that method for specific examples.
Since
v5.0.3
Development type aliases
WsServerStats
The WsServerStats type describes the number of paths and clients that are active on the WsServer.
{
paths: number;
clients: number;
}| Type | Description | |
|---|---|---|
paths | number | The number of paths currently being served by the |
clients | number | The number of clients currently being served by the |
A WsServerStats object is returned from the getStats method.
Since
v5.0.0
synchronizer-ws-server-durable-object
The synchronizer-ws-server-durable-object module of the TinyBase project lets you create a server that facilitates synchronization between clients, running as a Cloudflare Durable Object.
See also
- Cloudflare Durable Objects guide
- Synchronization guide
- Todo App v6 (collaboration) demo
Since
v5.4.0
Classes
WsServerDurableObject
The WsServerDurableObject is an overridden implementation of the DurableObject class, so you can have access to its members as well as the TinyBase-specific methods. If you are using the storage for other data, you may want to configure a prefix parameter to ensure you don't accidentally collide with TinyBase data.
Always remember to call the super implementations of the methods that TinyBase uses (the constructor, fetch, webSocketMessage, and webSocketClose) if you further override them.
Since
v5.4.0
Constructors
constructor
The constructor is used to create the Durable Object that will synchronize the TinyBase clients.
new WsServerDurableObject<Env>(
ctx: DurableObjectState,
env: Env,
): WsServerDurableObject<Env>| Type | Description | |
|---|---|---|
ctx | DurableObjectState | The DurableObjectState context. |
env | Env | The DurableObjectState environment. |
| returns | WsServerDurableObject<Env> | A new instance of the |
For basic TinyBase synchronization and persistence, you don't need to override this method, but if you do, ensure you call the super constructor with the two parameters.
Since
v5.4.0
Methods
Getter methods
getClientIds
The getClientIds method is used to access a list of all the connected clients on the path.
getClientIds(): IdsNote that if you call this method from within the onClientId method as a client is getting removed, it will still be returned in the list of client Ids.
Example
This example logs the list of clients being served by the Durable Object every time a synchronization method is handled.
import {WsServerDurableObject} from 'tinybase/synchronizers/synchronizer-ws-server-durable-object';
export class MyDurableObject extends WsServerDurableObject {
onMessage() {
console.info('Clients on path: ', this.getClientIds());
}
}
Since
v5.4.0
getPathId
The getPathId method is used to get the Id of the path that is being served.
getPathId(): string| returns | string | The |
|---|
This is useful for when you want to know which path the current Durable Object is serving - for the purposes of logging, for example.
Example
This example logs the path being served by the Durable Object every time a synchronization method is handled.
import {WsServerDurableObject} from 'tinybase/synchronizers/synchronizer-ws-server-durable-object';
export class MyDurableObject extends WsServerDurableObject {
onMessage() {
console.info('Message received on path: ', this.getPathId());
}
}
Since
v5.4.0
Creation methods
createPersister
The createPersister method is used to return a persister for the Durable Object to preserve Store data when clients are not connected.
createPersister(): undefined | Persister<MergeableStoreOnly> | Promise<Persister<MergeableStoreOnly>>| returns | undefined | Persister<MergeableStoreOnly> | Promise<Persister<MergeableStoreOnly>> | A new instance of a |
|---|
In other words, override this method to enable persistence of the Store data that the Durable Object is synchronizing between clients.
This should almost certainly return a DurableObjectStoragePersister, created with the createDurableObjectStoragePersister function. This will ensure that the Store is serialized to the Durable Object KV-based storage.
Returning undefined from this method will disable persistence.
Example
This example enables Durable Object persistence by creating a Persister object within the createPersister method of a WsServerDurableObject.
import {createMergeableStore} from 'tinybase';
import {createDurableObjectStoragePersister} from 'tinybase/persisters/persister-durable-object-storage';
import {WsServerDurableObject} from 'tinybase/synchronizers/synchronizer-ws-server-durable-object';
export class MyDurableObject extends WsServerDurableObject {
createPersister() {
const store = createMergeableStore();
const persister = createDurableObjectStoragePersister(
store,
this.ctx.storage,
);
return persister;
}
}
Since
v5.4.0
Event methods
onClientId
The onClientId method is called when a client connects to, or disconnects from, the server.
onClientId(
pathId: string,
clientId: string,
addedOrRemoved: IdAddedOrRemoved,
): void| Type | Description | |
|---|---|---|
pathId | string | The |
clientId | string | The |
addedOrRemoved | IdAddedOrRemoved | Whether the client is joining or leaving. |
| returns | void | This has no return value. |
This method is called with the path Id, the client Id, and an IdAddedOrRemoved flag indicating whether it this is being triggered by the client joining (1) or the client leaving (-1).
Note that if you call the getClientIds method from within this method as a client is getting removed, it will still be returned in the list of client Ids.
Example
This example logs every client that joins (the client Id is 'added') or leaves (the client Id is 'removed') on the path being served by the Durable Object.
import {WsServerDurableObject} from 'tinybase/synchronizers/synchronizer-ws-server-durable-object';
export class MyDurableObject extends WsServerDurableObject {
onClientId(pathId, clientId, addedOrRemoved) {
console.info(
(addedOrRemoved == 1 ? 'Added' : 'Removed') +
` client ${clientId} on path ${pathId}`,
);
}
}
Since
v5.4.0
onMessage
The onMessage method is called when a message is handled by the server.
onMessage(
fromClientId: string,
toClientId: string,
remainder: string,
): void| Type | Description | |
|---|---|---|
fromClientId | string | The |
toClientId | string | The |
remainder | string | The remainder of the body of the message. |
| returns | void | This has no return value. |
This is useful if you want to debug the synchronization process, though be aware that this method is called very frequently. It is called with the Id of the client the message came from, the Id of the client the message is to be forwarded to, and the remainder of the message itself.
Since this method is called often, it should be performant. The path Id is not passed as an argument, since it has a small cost to provide by default. You can use the getPathId method yourself if that information is needed.
Example
This example logs every message routed by the Durable Object between clients.
import {WsServerDurableObject} from 'tinybase/synchronizers/synchronizer-ws-server-durable-object';
export class MyDurableObject extends WsServerDurableObject {
onMessage(fromClientId, toClientId, remainder) {
console.info(
`Message from '${fromClientId}' to '${toClientId}': ${remainder}`,
);
}
}
Since
v5.4.0
onPathId
The onPathId method is called when the first client connects to, or the last client disconnects from, the server with a given path Id.
onPathId(
pathId: string,
addedOrRemoved: IdAddedOrRemoved,
): void| Type | Description | |
|---|---|---|
pathId | string | The |
addedOrRemoved | IdAddedOrRemoved | Whether the path had the first joiner, or the last leaver. |
| returns | void | This has no return value. |
This method is called with the path Id and an IdAddedOrRemoved flag indicating whether it this is being triggered by the first client joining (1) or the last client leaving (-1).
Example
This example logs the Id of the path being served by the Durable Object when the first client joins (the path Id is 'added'), and when the last client leaves (the path Id is 'removed').
import {WsServerDurableObject} from 'tinybase/synchronizers/synchronizer-ws-server-durable-object';
export class MyDurableObject extends WsServerDurableObject {
onPathId(pathId, addedOrRemoved) {
console.info(
(addedOrRemoved == 1 ? 'Added' : 'Removed') + ` path ${pathId}`,
);
}
}
Since
v5.4.0
Functions
getWsServerDurableObjectFetch
The getWsServerDurableObjectFetch function returns a convenient handler for a Cloudflare worker to route requests to the fetch handler of a WsServerDurableObject for the given namespace.
getWsServerDurableObjectFetch<Namespace>(namespace: Namespace): (request: Request, env: {[namespace in Namespace]: DurableObjectNamespace<WsServerDurableObject>}) => Response| Type | Description | |
|---|---|---|
namespace | Namespace | A string for the namespace of the Durable Objects that you want this worker to route requests to. |
| returns | (request: Request, env: {[namespace in Namespace]: DurableObjectNamespace<WsServerDurableObject>}) => Response | A fetch handler that routes WebSocket upgrade requests to a Durable Object. |
The implementation of the function that this returns requires the request to be a WebSocket 'Upgrade' request, and for the client to have provided a sec-websocket-key header that the server can use as a unique key for the client.
It then takes the path of the HTTP request and routes the upgrade request to a Durable Object (in the given namespace) for that path. From then on, the Durable Object handles all the WebSocket communication.
Note that you'll need to have a Wrangler configuration that connects your Durable Object class to the namespace. In other words, you'll have something like this in your wrangler.toml file.
[[durable_objects.bindings]]
name = "MyDurableObjects"
class_name = "MyDurableObject"
Note that it is not required to use this handler to route TinyBase client requests in your Cloudflare app. If you have your own custom routing logic, path scheme, or authentication, for example, you can easily implement that in the worker's fetch method yourself. See the Durable Objects documentation for examples.
You can also pass a newly created request to the Durable Object's fetch method. For example, you can overwrite the 'path' that the Durable Object thinks it is serving, perhaps to inject a unique authenticated user Id that wasn't actually provided by the client WebSocket.
Example
This example sets up default routing of the WebSocket upgrade request to a Durable Object in the MyDurableObjects namespace. This would require the wrangler.toml configuration shown above.
import {
WsServerDurableObject,
getWsServerDurableObjectFetch,
} from 'tinybase/synchronizers/synchronizer-ws-server-durable-object';
export class MyDurableObject extends WsServerDurableObject {}
export default {fetch: getWsServerDurableObjectFetch('MyDurableObjects')};
Since
v5.4.0
synchronizer-ws-server-simple
The synchronizer-ws-server-simple module of the TinyBase project lets you create a server that facilitates synchronization between clients, without the complications of listeners, persistence, or statistics.
This makes it more suitable to be used as a reference implementation for other server environments.
See also
- Synchronization guide
- Todo App v6 (collaboration) demo
Since
v5.4.0
Interfaces
WsServerSimple
The WsServerSimple interface represents an object that facilitates synchronization between clients that are using WsSynchronizer instances.
The core functionality is equivalent to the WsServer interface, but without the complications of listeners, persistence, or statistics.
You should use the createWsServerSimple function to create a WsServerSimple object.
Since
v5.4.0
destroy
The destroy method provides a way to clean up the server at the end of its use.
destroy(): Promise<void>| returns | Promise<void> |
|---|
This closes the underlying WebSocketServer that was provided when the WsServerSimple was created. This method is asynchronous.
Example
This example creates a WsServerSimple and then destroys it again, closing the underlying WebSocketServer.
import {createWsServerSimple} from 'tinybase/synchronizers/synchronizer-ws-server-simple';
import {WebSocketServer} from 'ws';
const webSocketServer = new WebSocketServer({port: 8053});
webSocketServer.on('close', () => {
console.log('WebSocketServer closed');
});
const server = createWsServerSimple(webSocketServer);
await server.destroy();
// ...
// -> 'WebSocketServer closed'
Since
v5.4.0
getWebSocketServer
The getWebSocketServer method returns a reference to the WebSocketServer being used for this WsServerSimple.
getWebSocketServer(): WebSocketServer| returns | WebSocketServer | The WebSocketServer reference. |
|---|
Example
This example creates a WsServerSimple and then gets the WebSocketServer reference back out again.
import {createWsServerSimple} from 'tinybase/synchronizers/synchronizer-ws-server-simple';
import {WebSocketServer} from 'ws';
const webSocketServer = new WebSocketServer({port: 8053});
const server = createWsServerSimple(webSocketServer);
console.log(server.getWebSocketServer() == webSocketServer);
// -> true
await server.destroy();
Since
v5.4.0
Functions
createWsServerSimple
The createWsServerSimple function creates a WsServerSimple that facilitates synchronization between clients that are using WsSynchronizer instances.
createWsServerSimple(webSocketServer: WebSocketServer): WsServerSimple| Type | Description | |
|---|---|---|
webSocketServer | WebSocketServer | A WebSocketServer object from your server environment. |
| returns | WsServerSimple | A reference to the new |
This should be run in a server environment, and you must pass in a configured WebSocketServer object in order to create it.
The core functionality is equivalent to the WsServer interface, but without the complications of listeners, persistence, or statistics. This makes it more suitable to be used as a reference implementation for other server environments.
Example
This example creates a WsServerSimple that synchronizes two clients on a shared path.
import {createMergeableStore} from 'tinybase';
import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';
import {createWsServerSimple} from 'tinybase/synchronizers/synchronizer-ws-server-simple';
import {WebSocket, WebSocketServer} from 'ws';
// Server
const server = createWsServerSimple(new WebSocketServer({port: 8053}));
// Client 1
const clientStore1 = createMergeableStore();
clientStore1.setCell('pets', 'fido', 'species', 'dog');
const synchronizer1 = await createWsSynchronizer(
clientStore1,
new WebSocket('ws://localhost:8053/petShop'),
);
await synchronizer1.startSync();
// ...
// Client 2
const clientStore2 = createMergeableStore();
clientStore2.setCell('pets', 'felix', 'species', 'cat');
const synchronizer2 = await createWsSynchronizer(
clientStore2,
new WebSocket('ws://localhost:8053/petShop'),
);
await synchronizer2.startSync();
// ...
console.log(clientStore1.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
console.log(clientStore2.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
await synchronizer1.destroy();
await synchronizer2.destroy();
await server.destroy();
Since
v5.4.0
ui-react
The ui-react module of the TinyBase project provides both hooks and components to make it easy to create reactive apps with Store objects.
The hooks in this module provide access to the data and structures exposed by other modules in the project. As well as immediate access, they all register listeners such that components using those hooks are selectively re-rendered when data changes.
The components in this module provide a further abstraction over those hooks to ease the composition of user interfaces that use TinyBase.
See also
- Building UIs guides
- Building UIs With
Metricsguide - Building UIs With
Indexesguide - Building UIs With
Relationshipsguide - Building UIs With
Queriesguide - Building UIs With
Checkpointsguide - Countries demo
- Todo App demos
- Drawing demo
Since
v1.0.0
Functions
Checkpoints hooks
useCheckpoint
The useCheckpoint hook returns the label for a checkpoint, and registers a listener so that any changes to that result will cause a re-render.
useCheckpoint(
checkpointId: string,
checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId,
): string | undefined| Type | Description | |
|---|---|---|
checkpointId | string | The |
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
| returns | string | undefined | A string label for the requested checkpoint, an empty string if it was never set, or |
A Provider component is used to wrap part of an application in a context, and it can contain a default Checkpoints object or a set of Checkpoints objects named by Id. The useCheckpoint hook lets you indicate which Checkpoints object to get data for: omit the optional final parameter for the default context Checkpoints object, provide an Id for a named context Checkpoints object, or provide a Checkpoints object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the label will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Checkpoints object outside the application, which is used in the useCheckpoint hook by reference. A change to the checkpoint label re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createCheckpoints, createStore} from 'tinybase';
import {useCheckpoint} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {fido: {species: 'dog'}});
const checkpoints = createCheckpoints(store);
const App = () => <span>{useCheckpoint('1', checkpoints)}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span></span>'
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(app.innerHTML);
// -> '<span>sale</span>'
This example creates a Provider context into which a default Checkpoints object is provided. A component within it then uses the useCheckpoint hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createCheckpoints, createStore} from 'tinybase';
import {Provider, useCheckpoint} from 'tinybase/ui-react';
const App = ({checkpoints}) => (
<Provider checkpoints={checkpoints}>
<Pane />
</Provider>
);
const Pane = () => <span>{useCheckpoint('0')}</span>;
const checkpoints = createCheckpoints(
createStore().setTable('pets', {fido: {species: 'dog'}}),
).setCheckpoint('0', 'initial');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(app.innerHTML);
// -> '<span>initial</span>'
This example creates a Provider context into which a default Checkpoints object is provided. A component within it then uses the useCheckpoint hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createCheckpoints, createStore} from 'tinybase';
import {Provider, useCheckpoint} from 'tinybase/ui-react';
const App = ({checkpoints}) => (
<Provider checkpointsById={{petCheckpoints: checkpoints}}>
<Pane />
</Provider>
);
const Pane = () => <span>{useCheckpoint('0', 'petCheckpoints')}</span>;
const checkpoints = createCheckpoints(
createStore().setTable('pets', {fido: {species: 'dog'}}),
).setCheckpoint('0', 'initial');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(app.innerHTML);
// -> '<span>initial</span>'
Since
v1.0.0
useCheckpointIds
The useCheckpointIds hook returns an array of the checkpoint Ids being managed by this Checkpoints object, and registers a listener so that any changes to that result will cause a re-render.
useCheckpointIds(checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId): CheckpointIds| Type | Description | |
|---|---|---|
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
| returns | CheckpointIds | A |
A Provider component is used to wrap part of an application in a context, and it can contain a default Checkpoints object or a set of Checkpoints objects named by Id. The useCheckpointIds hook lets you indicate which Checkpoints object to get data for: omit the optional parameter for the default context Checkpoints object, provide an Id for a named context Checkpoints object, or provide a Checkpoints object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the checkpoint Ids will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Checkpoints object outside the application, which is used in the useCheckpointIds hook by reference. A change to the checkpoint Ids re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createCheckpoints, createStore} from 'tinybase';
import {useCheckpointIds} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {fido: {species: 'dog'}});
const checkpoints = createCheckpoints(store);
const App = () => (
<span>{JSON.stringify(useCheckpointIds(checkpoints))}</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>[[],"0",[]]</span>'
store.setCell('pets', 'fido', 'sold', true);
console.log(app.innerHTML);
// -> '<span>[["0"],null,[]]</span>'
checkpoints.addCheckpoint('sale');
console.log(app.innerHTML);
// -> '<span>[["0"],"1",[]]</span>'
This example creates a Provider context into which a default Checkpoints object is provided. A component within it then uses the useCheckpointIds hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createCheckpoints, createStore} from 'tinybase';
import {Provider, useCheckpointIds} from 'tinybase/ui-react';
const App = ({checkpoints}) => (
<Provider checkpoints={checkpoints}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useCheckpointIds())}</span>;
const checkpoints = createCheckpoints(
createStore().setTable('pets', {fido: {species: 'dog'}}),
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(app.innerHTML);
// -> '<span>[[],"0",[]]</span>'
This example creates a Provider context into which a default Checkpoints object is provided. A component within it then uses the useCheckpointIds hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createCheckpoints, createStore} from 'tinybase';
import {Provider, useCheckpointIds} from 'tinybase/ui-react';
const App = ({checkpoints}) => (
<Provider checkpointsById={{petCheckpoints: checkpoints}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useCheckpointIds('petCheckpoints'))}</span>
);
const checkpoints = createCheckpoints(
createStore().setTable('pets', {fido: {species: 'dog'}}),
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(app.innerHTML);
// -> '<span>[[],"0",[]]</span>'
Since
v1.0.0
useCheckpointIdsListener
The useCheckpointIdsListener hook registers a listener function with the Checkpoints object that will be called whenever its set of checkpoints changes.
useCheckpointIdsListener(
listener: CheckpointIdsListener,
listenerDeps?: DependencyList,
checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId,
): void| Type | Description | |
|---|---|---|
listener | CheckpointIdsListener | The function that will be called whenever the checkpoints change. |
listenerDeps? | DependencyList | An optional array of dependencies for the |
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useCheckpointIds hook).
Unlike the addCheckpointIdsListener method, which returns a listener Id and requires you to remove it manually, the useCheckpointIdsListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Checkpoints object will be deleted.
Example
This example uses the useCheckpointIdsListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createCheckpoints, createStore} from 'tinybase';
import {Provider, useCheckpointIdsListener} from 'tinybase/ui-react';
const App = ({checkpoints}) => (
<Provider checkpoints={checkpoints}>
<Pane />
</Provider>
);
const Pane = () => {
useCheckpointIdsListener(() => console.log('Checkpoint Ids changed'));
return <span>App</span>;
};
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(checkpoints.getListenerStats().checkpointIds);
// -> 1
store.setCell('pets', 'fido', 'sold', true);
// -> 'Checkpoint Ids changed'
checkpoints.addCheckpoint();
// -> 'Checkpoint Ids changed'
root.unmount();
console.log(checkpoints.getListenerStats().checkpointIds);
// -> 0
Since
v1.0.0
useCheckpointListener
The useCheckpointListener hook registers a listener function with the Checkpoints object that will be called whenever the label of a checkpoint changes.
useCheckpointListener(
checkpointId: IdOrNull,
listener: CheckpointListener,
listenerDeps?: DependencyList,
checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId,
): void| Type | Description | |
|---|---|---|
checkpointId | IdOrNull | The |
listener | CheckpointListener | The function that will be called whenever the checkpoint label changes. |
listenerDeps? | DependencyList | An optional array of dependencies for the |
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useCheckpoint hook).
You can either listen to a single checkpoint label (by specifying the checkpoint Id as the method's first parameter), or changes to any checkpoint label (by providing a null wildcard).
Unlike the addCheckpointListener method, which returns a listener Id and requires you to remove it manually, the useCheckpointListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Checkpoints object will be deleted.
Example
This example uses the useCheckpointListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createCheckpoints, createStore} from 'tinybase';
import {Provider, useCheckpointListener} from 'tinybase/ui-react';
const App = ({checkpoints}) => (
<Provider checkpoints={checkpoints}>
<Pane />
</Provider>
);
const Pane = () => {
useCheckpointListener('0', () =>
console.log('Checkpoint label changed'),
);
return <span>App</span>;
};
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(checkpoints.getListenerStats().checkpoint);
// -> 1
checkpoints.setCheckpoint('0', 'initial');
// -> 'Checkpoint label changed'
root.unmount();
console.log(checkpoints.getListenerStats().checkpoint);
// -> 0
Since
v1.0.0
useCheckpoints
The useCheckpoints hook is used to get a reference to a Checkpoints object from within a Provider component context.
useCheckpoints(id?: string): Checkpoints | undefined| Type | Description | |
|---|---|---|
id? | string | An optional |
| returns | Checkpoints | undefined | A reference to the |
A Provider component is used to wrap part of an application in a context. It can contain a default Checkpoints object (or a set of Checkpoints objects named by Id) that can be easily accessed without having to be passed down as props through every component.
The useCheckpoints hook lets you either get a reference to the default Checkpoints object (when called without a parameter), or one of the Checkpoints objects that are named by Id (when called with an Id parameter).
Examples
This example creates a Provider context into which a default Checkpoint object is provided. A component within it then uses the useCheckpoints hook to get a reference to the Checkpoints object again, without the need to have it passed as a prop.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createCheckpoints, createStore} from 'tinybase';
import {Provider, useCheckpoints} from 'tinybase/ui-react';
const App = ({checkpoints}) => (
<Provider checkpoints={checkpoints}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useCheckpoints().getListenerStats().checkpointIds}</span>
);
const checkpoints = createCheckpoints(createStore());
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
This example creates a Provider context into which a Checkpoints object is provided, named by Id. A component within it then uses the useCheckpoints hook with that Id to get a reference to the Checkpoints object again, without the need to have it passed as a prop.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createCheckpoints, createStore} from 'tinybase';
import {Provider, useCheckpoints} from 'tinybase/ui-react';
const App = ({checkpoints}) => (
<Provider checkpointsById={{petStore: checkpoints}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{useCheckpoints('petStore').getListenerStats().checkpointIds}
</span>
);
const checkpoints = createCheckpoints(createStore());
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
Since
v1.0.0
useCheckpointsIds
The useCheckpointsIds hook is used to retrieve the Ids of all the named Checkpoints objects present in the current Provider component context.
useCheckpointsIds(): IdsExample
This example adds two named Checkpoints objects to a Provider context and an inner component accesses their Ids.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createCheckpoints, createStore} from 'tinybase';
import {
Provider,
useCheckpointsIds,
useCreateCheckpoints,
useCreateStore,
} from 'tinybase/ui-react';
const App = () => {
const store1 = useCreateStore(createStore);
const checkpoints1 = useCreateCheckpoints(store1, createCheckpoints);
const store2 = useCreateStore(createStore);
const checkpoints2 = useCreateCheckpoints(store2, createCheckpoints);
return (
<Provider checkpointsById={{checkpoints1, checkpoints2}}>
<Pane />
</Provider>
);
};
const Pane = () => <span>{JSON.stringify(useCheckpointsIds())}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["checkpoints1","checkpoints2"]</span>'
Since
v4.1.0
useCheckpointsOrCheckpointsById
The useCheckpointsOrCheckpointsById hook is used to get a reference to a Checkpoints object from within a Provider component context, or have it passed directly to this hook.
useCheckpointsOrCheckpointsById(checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId): Checkpoints | undefined| Type | Description | |
|---|---|---|
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | Either an |
| returns | Checkpoints | undefined | A reference to the |
This is mostly of use when you are developing a component that needs a Checkpoints object and which might have been passed in explicitly to the component or is to be picked up from the context by Id (a common pattern for Checkpoints-based components).
This is unlikely to be used often. For most situations, you will want to use the useCheckpoints hook.
Example
This example creates a Provider context into which a default Checkpoints object is provided. A component within it then uses the useCheckpointsOrCheckpointsById hook to get a reference to the Checkpoints object again, without the need to have it passed as a prop. Note however, that unlike the useCheckpoints hook example, this component would also work if you were to pass the Checkpoints object directly into it, making it more portable.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createCheckpoints, createStore} from 'tinybase';
import {
Provider,
useCheckpointsOrCheckpointsById,
} from 'tinybase/ui-react';
const App = ({checkpoints}) => (
<Provider checkpoints={checkpoints}>
<Pane />
</Provider>
);
const Pane = ({checkpoints}) => (
<span>
{JSON.stringify(
useCheckpointsOrCheckpointsById(checkpoints).getCheckpointIds(),
)}
</span>
);
const checkpoints = createCheckpoints(createStore());
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(app.innerHTML);
// -> '<span>[[],"0",[]]</span>'
Since
v4.1.0
useCreateCheckpoints
The useCreateCheckpoints hook is used to create a Checkpoints object within a React application with convenient memoization.
useCreateCheckpoints(
store: undefined | Store,
create: (store: Store) => Checkpoints,
createDeps?: DependencyList,
): Checkpoints | undefined| Type | Description | |
|---|---|---|
store | undefined | Store | A reference to the |
create | (store: Store) => Checkpoints | A function for performing the creation steps of the |
createDeps? | DependencyList | An optional array of dependencies for the |
| returns | Checkpoints | undefined | A reference to the |
It is possible to create a Checkpoints object outside of the React app with the regular createCheckpoints function and pass it in, but you may prefer to create it within the app, perhaps inside the top-level component. To prevent a new Checkpoints object being created every time the app renders or re-renders, since v5.0 this hook performs the creation in an effect. As a result it will return undefined on the brief first render (or if the Store is not yet defined), which you should defend against.
If your create function contains other dependencies, the changing of which should also cause the Checkpoints object to be recreated, you can provide them in an array in the optional second parameter, just as you would for any React hook with dependencies.
This hook ensures the Checkpoints object is destroyed whenever a new one is created or the component is unmounted.
Examples
This example creates a Checkpoints object at the top level of a React application. Even though the App component is rendered twice, the Checkpoints object creation only occurs once by default.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createCheckpoints, createStore} from 'tinybase';
import {useCreateCheckpoints, useCreateStore} from 'tinybase/ui-react';
const App = () => {
const store = useCreateStore(createStore);
const checkpoints = useCreateCheckpoints(store, (store) => {
console.log('Checkpoints created');
return createCheckpoints(store).setSize(10);
});
return <span>{JSON.stringify(checkpoints?.getCheckpointIds())}</span>;
};
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
// -> 'Checkpoints created'
root.render(<App />);
// No second Checkpoints creation
console.log(app.innerHTML);
// -> '<span>[[],"0",[]]</span>'
This example creates a Checkpoints object at the top level of a React application. The App component is rendered twice, each with a different top-level prop. The useCreateCheckpoints hook takes the size prop as a dependency, and so the Checkpoints object is created again on the second render.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createCheckpoints, createStore} from 'tinybase';
import {useCreateCheckpoints, useCreateStore} from 'tinybase/ui-react';
const App = ({size}) => {
const store = useCreateStore(createStore);
const checkpoints = useCreateCheckpoints(
store,
(store) => {
console.log(`Checkpoints created, size ${size}`);
return createCheckpoints(store).setSize(size);
},
[size],
);
return <span>{JSON.stringify(checkpoints?.getCheckpointIds())}</span>;
};
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App size={20} />);
// -> 'Checkpoints created, size 20'
console.log(app.innerHTML);
// -> '<span>[[],"0",[]]</span>'
root.render(<App size={50} />);
// -> 'Checkpoints created, size 50'
console.log(app.innerHTML);
// -> '<span>[[],"0",[]]</span>'
Since
v1.0.0
useGoBackwardCallback
The useGoBackwardCallback hook returns a callback that moves the state of the underlying Store back to the previous checkpoint, effectively performing an 'undo' on the Store data.
useGoBackwardCallback(checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId): Callback| Type | Description | |
|---|---|---|
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
| returns | Callback | A callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will go backward to the previous checkpoint - such as when clicking an undo button.
If there is no previous checkpoint to return to, this callback has no effect.
Example
This example uses the useGoBackwardCallback hook to create an event handler which goes backward in the checkpoint stack when the span element is clicked.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createCheckpoints, createStore} from 'tinybase';
import {useGoBackwardCallback} from 'tinybase/ui-react';
const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const checkpoints = createCheckpoints(store);
const App = () => (
<span id="span" onClick={useGoBackwardCallback(checkpoints)}>
Backward
</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
const _span = app.querySelector('span');
store.setCell('pets', 'nemo', 'color', 'orange');
checkpoints.addCheckpoint();
console.log(checkpoints.getCheckpointIds());
// -> [["0"], "1", []]
// User clicks the <span> element:
// -> _span MouseEvent('click', {bubbles: true})
console.log(checkpoints.getCheckpointIds());
// -> [[], "0", ["1"]]
Since
v1.0.0
useGoForwardCallback
The useGoForwardCallback hook returns a callback that moves the state of the underlying Store forwards to a future checkpoint, effectively performing an 'redo' on the Store data.
useGoForwardCallback(checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId): Callback| Type | Description | |
|---|---|---|
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
| returns | Callback | A callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will go forward to the next checkpoint - such as when clicking an redo button.
If there is no future checkpoint to return to, this callback has no effect.
Example
This example uses the useGoForwardCallback hook to create an event handler which goes backward in the checkpoint stack when the span element is clicked.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createCheckpoints, createStore} from 'tinybase';
import {useGoForwardCallback} from 'tinybase/ui-react';
const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const checkpoints = createCheckpoints(store);
const App = () => (
<span id="span" onClick={useGoForwardCallback(checkpoints)}>
Forward
</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
const _span = app.querySelector('span');
store.setCell('pets', 'nemo', 'color', 'orange');
checkpoints.addCheckpoint();
console.log(checkpoints.getCheckpointIds());
// -> [["0"], "1", []]
checkpoints.goBackward();
console.log(checkpoints.getCheckpointIds());
// -> [[], "0", ["1"]]
// User clicks the <span> element:
// -> _span MouseEvent('click', {bubbles: true})
console.log(checkpoints.getCheckpointIds());
// -> [["0"], "1", []]
Since
v1.0.0
useGoToCallback
The useGoToCallback hook returns a parameterized callback that can be used to move the state of the underlying Store backwards or forwards to a specified checkpoint.
useGoToCallback<Parameter>(
getCheckpointId: (parameter: Parameter) => string,
getCheckpointIdDeps?: DependencyList,
checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId,
then?: (checkpoints: Checkpoints, checkpointId: string) => void,
thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>| Type | Description | |
|---|---|---|
getCheckpointId | (parameter: Parameter) => string | A function which returns an |
getCheckpointIdDeps? | DependencyList | An optional array of dependencies for the |
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
then? | (checkpoints: Checkpoints, checkpointId: string) => void | A function which is called after the checkpoint is moved, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
| returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. This parameter defaults to an empty array. |
This hook is useful, for example, when creating an event handler that will move the checkpoint. In this case, the parameter will likely be the event, so that you can use data from it as the checkpoint Id to move to.
The optional first parameter is a function which will produce the label that will then be used to name the checkpoint.
If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional second parameter, just as you would for any React hook with dependencies.
For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the checkpoint has been set.
The Checkpoints object for which the callback will set the checkpoint (indicated by the hook's checkpointsOrCheckpointsId parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useGoToCallback hook to create an event handler which moves to a checkpoint when the span element is clicked.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createCheckpoints, createStore} from 'tinybase';
import {useGoToCallback} from 'tinybase/ui-react';
const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const checkpoints = createCheckpoints(store);
const App = () => {
const handleClick = useGoToCallback(() => '0', [], checkpoints);
return (
<span id="span" onClick={handleClick}>
Goto 0
</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
const _span = app.querySelector('span');
store.setCell('pets', 'nemo', 'color', 'orange');
checkpoints.addCheckpoint();
console.log(checkpoints.getCheckpointIds());
// -> [["0"], "1", []]
// User clicks the <span> element:
// -> _span MouseEvent('click', {bubbles: true})
console.log(checkpoints.getCheckpointIds());
// -> [[], "0", ["1"]]
Since
v1.0.0
useProvideCheckpoints
The useProvideCheckpoints hook is used to add a Checkpoints object by Id to a Provider component, but imperatively from a component within it.
useProvideCheckpoints(
checkpointsId: string,
checkpoints: Checkpoints,
): void| Type | Description | |
|---|---|---|
checkpointsId | string | The |
checkpoints | Checkpoints | The |
| returns | void | This has no return value. |
Normally you will register a Checkpoints object by Id in a context by using the checkpointsById prop of the top-level Provider component. This hook, however, lets you dynamically add a new Checkpoints object to the context, from within a component. This is useful for applications where the set of Checkpoints objects is not known at the time of the first render of the root Provider.
A Checkpoints object added to the Provider context in this way will be available to other components within the context (using the useCheckpoints hook and so on). If you use the same Id as an existing Checkpoints object registration, the new one will take priority over one provided by the checkpointsById prop.
Note that other components that consume a Checkpoints object registered like this should defend against it being undefined at first. On the first render, the other component will likely not yet have completed the registration. In the example below, we use the null-safe useCheckpoints('petCheckpoints')? to do this.
Example
This example creates a Provider context. A child component registers a Checkpoints object into it which is then consumable by a peer child component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createCheckpoints, createStore} from 'tinybase';
import {
Provider,
useCheckpoints,
useCreateCheckpoints,
useCreateStore,
useProvideCheckpoints,
} from 'tinybase/ui-react';
const App = () => (
<Provider>
<RegisterCheckpoints />
<ConsumeCheckpoints />
</Provider>
);
const RegisterCheckpoints = () => {
const store = useCreateStore(() =>
createStore().setCell('pets', 'fido', 'color', 'brown'),
);
const checkpoints = useCreateCheckpoints(store, createCheckpoints);
useProvideCheckpoints('petCheckpoints', checkpoints);
return null;
};
const ConsumeCheckpoints = () => (
<span>
{JSON.stringify(useCheckpoints('petCheckpoints')?.getCheckpointIds())}
</span>
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
console.log(app.innerHTML);
// -> '<span>[[],"0",[]]</span>'
Since
v5.3.0
useRedoInformation
The useRedoInformation hook returns an UndoOrRedoInformation array that indicates if and how you can move the state of the underlying Store forwards to a future checkpoint.
useRedoInformation(checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId): UndoOrRedoInformation| Type | Description | |
|---|---|---|
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
| returns | UndoOrRedoInformation |
|
This hook is useful if you are building an redo button: the information contains whether a redo action is available (to enable the button), the callback to perform the redo action, the checkpoint Id that will be redone, and its label, if available.
Example
This example uses the useUndoInformation hook to create a redo button.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createCheckpoints, createStore} from 'tinybase';
import {useRedoInformation} from 'tinybase/ui-react';
const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const checkpoints = createCheckpoints(store);
const App = () => {
const [canRedo, handleRedo, _id, label] =
useRedoInformation(checkpoints);
return canRedo ? (
<span onClick={handleRedo}>Redo {label}</span>
) : (
<span>Nothing to redo</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>Nothing to redo</span>'
store.setCell('pets', 'nemo', 'color', 'orange');
checkpoints.addCheckpoint('color');
checkpoints.goTo('0');
console.log(app.innerHTML);
// -> '<span>Redo color</span>'
Since
v1.0.0
useSetCheckpointCallback
The useSetCheckpointCallback hook returns a parameterized callback that can be used to record a checkpoint of a Store into a Checkpoints object that can be reverted to in the future.
useSetCheckpointCallback<Parameter>(
getCheckpoint?: (parameter: Parameter) => string,
getCheckpointDeps?: DependencyList,
checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId,
then?: (checkpointId: string, checkpoints: Checkpoints, label?: string) => void,
thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>| Type | Description | |
|---|---|---|
getCheckpoint? | (parameter: Parameter) => string | An optional function which returns a string that will be used to describe the actions leading up to this checkpoint, based on the parameter the callback will receive (and which is most likely a DOM event). |
getCheckpointDeps? | DependencyList | An optional array of dependencies for the |
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
then? | (checkpointId: string, checkpoints: Checkpoints, label?: string) => void | A function which is called after the checkpoint is set, with the new checkpoint |
thenDeps? | DependencyList | An optional array of dependencies for the |
| returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will set the checkpoint. In this case, the parameter will likely be the event, so that you can use data from it as the checkpoint label.
The optional first parameter is a function which will produce the label that will then be used to name the checkpoint.
If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional second parameter, just as you would for any React hook with dependencies.
For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the checkpoint has been set.
The Checkpoints object for which the callback will set the checkpoint (indicated by the hook's checkpointsOrCheckpointsId parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useSetCheckpointCallback hook to create an event handler which sets a checkpoint when the span element is clicked.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createCheckpoints, createStore} from 'tinybase';
import {useSetCheckpointCallback} from 'tinybase/ui-react';
const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const checkpoints = createCheckpoints(store);
const App = () => {
const handleClick = useSetCheckpointCallback(
(e) => `with #${e.target.id} button`,
[],
checkpoints,
(checkpointId, checkpoints, label) =>
console.log(`Checkpoint ${checkpointId} set, ${label}`),
);
return (
<span id="span" onClick={handleClick}>
Set
</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
const _span = app.querySelector('span');
store.setCell('pets', 'nemo', 'color', 'orange');
// User clicks the <span> element:
// -> _span MouseEvent('click', {bubbles: true})
// -> 'Checkpoint 1 set, with #span button'
Since
v1.0.0
useUndoInformation
The useUndoInformation hook returns an UndoOrRedoInformation array that indicates if and how you can move the state of the underlying Store backward to the previous checkpoint.
useUndoInformation(checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId): UndoOrRedoInformation| Type | Description | |
|---|---|---|
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
| returns | UndoOrRedoInformation |
|
This hook is useful if you are building an undo button: the information contains whether an undo action is available (to enable the button), the callback to perform the undo action, the current checkpoint Id that will be undone, and its label, if available.
Example
This example uses the useUndoInformation hook to create an undo button.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createCheckpoints, createStore} from 'tinybase';
import {useUndoInformation} from 'tinybase/ui-react';
const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const checkpoints = createCheckpoints(store);
const App = () => {
const [canUndo, handleUndo, _id, label] =
useUndoInformation(checkpoints);
return canUndo ? (
<span onClick={handleUndo}>Undo {label}</span>
) : (
<span>Nothing to undo</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>Nothing to undo</span>'
store.setCell('pets', 'nemo', 'color', 'orange');
checkpoints.addCheckpoint('color');
console.log(app.innerHTML);
// -> '<span>Undo color</span>'
Since
v1.0.0
Indexes hooks
useSliceRowIds
The useSliceRowIds hook gets the list of Row Ids in a given Slice, and registers a listener so that any changes to that result will cause a re-render.
useSliceRowIds(
indexId: string,
sliceId: string,
indexesOrIndexesId?: IndexesOrIndexesId,
): Ids| Type | Description | |
|---|---|---|
indexId | string | |
sliceId | string | |
indexesOrIndexesId? | IndexesOrIndexesId | The |
| returns | Ids |
A Provider component is used to wrap part of an application in a context, and it can contain a default Indexes object or a set of Indexes objects named by Id. The useSliceRowIds hook lets you indicate which Indexes object to get data for: omit the optional final parameter for the default context Indexes object, provide an Id for a named context Indexes object, or provide an Indexes object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Row Ids in the Slice will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates an Indexes object outside the application, which is used in the useSliceRowIds hook by reference. A change to the Row Ids in the Slice re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createIndexes, createStore} from 'tinybase';
import {useSliceRowIds} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const App = () => (
<span>
{JSON.stringify(useSliceRowIds('bySpecies', 'dog', indexes))}
</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["fido","cujo"]</span>'
store.setRow('pets', 'toto', {species: 'dog'});
console.log(app.innerHTML);
// -> '<span>["fido","cujo","toto"]</span>'
This example creates a Provider context into which a default Indexes object is provided. A component within it then uses the useSliceRowIds hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createIndexes, createStore} from 'tinybase';
import {Provider, useSliceRowIds} from 'tinybase/ui-react';
const App = ({indexes}) => (
<Provider indexes={indexes}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useSliceRowIds('bySpecies', 'dog'))}</span>
);
const indexes = createIndexes(
createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
}),
).setIndexDefinition('bySpecies', 'pets', 'species');
const app = document.createElement('div');
createRoot(app).render(<App indexes={indexes} />);
console.log(app.innerHTML);
// -> '<span>["fido","cujo"]</span>'
This example creates a Provider context into which a default Indexes object is provided. A component within it then uses the useSliceRowIds hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createIndexes, createStore} from 'tinybase';
import {Provider, useSliceRowIds} from 'tinybase/ui-react';
const App = ({indexes}) => (
<Provider indexesById={{petIndexes: indexes}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{JSON.stringify(useSliceRowIds('bySpecies', 'dog', 'petIndexes'))}
</span>
);
const indexes = createIndexes(
createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
}),
).setIndexDefinition('bySpecies', 'pets', 'species');
const app = document.createElement('div');
createRoot(app).render(<App indexes={indexes} />);
console.log(app.innerHTML);
// -> '<span>["fido","cujo"]</span>'
Since
v1.0.0
useSliceRowIdsListener
The useSliceRowIdsListener hook registers a listener function with the Indexes object that will be called whenever the Row Ids in a Slice change.
useSliceRowIdsListener(
indexId: IdOrNull,
sliceId: IdOrNull,
listener: SliceRowIdsListener,
listenerDeps?: DependencyList,
indexesOrIndexesId?: IndexesOrIndexesId,
): void| Type | Description | |
|---|---|---|
indexId | IdOrNull | |
sliceId | IdOrNull | |
listener | SliceRowIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
indexesOrIndexesId? | IndexesOrIndexesId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useSliceRowIds hook).
You can either listen to a single Slice (by specifying the Index Id and Slice Id as the method's first two parameters), or changes to any Slice (by providing null wildcards).
Both, either, or neither of the indexId and sliceId parameters can be wildcarded with null. You can listen to a specific Slice in a specific Index, any Slice in a specific Index, a specific Slice in any Index, or any Slice in any Index.
Unlike the addSliceRowIdsListener method, which returns a listener Id and requires you to remove it manually, the useSliceRowIdsListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Indexes object will be deleted.
Example
This example uses the useSliceRowIdsListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Indexes object.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createIndexes, createStore} from 'tinybase';
import {Provider, useSliceRowIdsListener} from 'tinybase/ui-react';
const App = ({indexes}) => (
<Provider indexes={indexes}>
<Pane />
</Provider>
);
const Pane = () => {
useSliceRowIdsListener('bySpecies', 'dog', () =>
console.log('Slice Row Ids changed'),
);
return <span>App</span>;
};
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App indexes={indexes} />);
console.log(indexes.getListenerStats().sliceRowIds);
// -> 1
store.setRow('pets', 'toto', {species: 'dog'});
// -> 'Slice Row Ids changed'
root.unmount();
console.log(indexes.getListenerStats().sliceRowIds);
// -> 0
Since
v1.0.0
useCreateIndexes
The useCreateIndexes hook is used to create an Indexes object within a React application with convenient memoization.
useCreateIndexes(
store: undefined | Store,
create: (store: Store) => Indexes,
createDeps?: DependencyList,
): Indexes | undefined| Type | Description | |
|---|---|---|
store | undefined | Store | A reference to the |
create | (store: Store) => Indexes | A function for performing the creation steps of the |
createDeps? | DependencyList | An optional array of dependencies for the |
| returns | Indexes | undefined | A reference to the |
It is possible to create an Indexes object outside of the React app with the regular createIndexes function and pass it in, but you may prefer to create it within the app, perhaps inside the top-level component. To prevent a new Indexes object being created every time the app renders or re-renders, since v5.0 the this hook performs the creation in an effect. As a result it will return undefined on the brief first render (or if the Store is not yet defined), which you should defend against.
If your create function contains other dependencies, the changing of which should also cause the Indexes object to be recreated, you can provide them in an array in the optional second parameter, just as you would for any React hook with dependencies.
This hook ensures the Indexes object is destroyed whenever a new one is created or the component is unmounted.
Examples
This example creates an Indexes object at the top level of a React application. Even though the App component is rendered twice, the Indexes object creation only occurs once by default.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createIndexes, createStore} from 'tinybase';
import {useCreateIndexes, useCreateStore} from 'tinybase/ui-react';
const App = () => {
const store = useCreateStore(() =>
createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
}),
);
const indexes = useCreateIndexes(store, (store) => {
console.log('Indexes created');
return createIndexes(store).setIndexDefinition(
'bySpecies',
'pets',
'species',
);
});
return <span>{JSON.stringify(indexes?.getSliceIds('bySpecies'))}</span>;
};
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
// -> 'Indexes created'
root.render(<App />);
// No second Indexes creation
console.log(app.innerHTML);
// -> '<span>["dog","cat"]</span>'
This example creates an Indexes object at the top level of a React application. The App component is rendered twice, each with a different top-level prop. The useCreateIndexes hook takes the cellToIndex prop as a dependency, and so the Indexes object is created again on the second render.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createIndexes, createStore} from 'tinybase';
import {useCreateIndexes, useCreateStore} from 'tinybase/ui-react';
const App = ({cellToIndex}) => {
const store = useCreateStore(() =>
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'brown'},
}),
);
const indexes = useCreateIndexes(
store,
(store) => {
console.log(`Index created for ${cellToIndex} cell`);
return createIndexes(store).setIndexDefinition(
'byCell',
'pets',
cellToIndex,
);
},
[cellToIndex],
);
return <span>{JSON.stringify(indexes?.getSliceIds('byCell'))}</span>;
};
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App cellToIndex="species" />);
// -> 'Index created for species cell'
console.log(app.innerHTML);
// -> '<span>["dog","cat"]</span>'
root.render(<App cellToIndex="color" />);
// -> 'Index created for color cell'
console.log(app.innerHTML);
// -> '<span>["brown","black"]</span>'
Since
v1.0.0
useIndexIds
The useIndexIds hook gets an array of the Index Ids registered with an Indexes object, and registers a listener so that any changes to that result will cause a re-render.
useIndexIds(indexesOrIndexesId?: IndexesOrIndexesId): Ids| Type | Description | |
|---|---|---|
indexesOrIndexesId? | IndexesOrIndexesId | The |
| returns | Ids |
A Provider component is used to wrap part of an application in a context, and it can contain a default Indexes object or a set of Indexes objects named by Id. The useIndexIds hook lets you indicate which Indexes object to get data for: omit the optional final parameter for the default context Indexes object, provide an Id for a named context Indexes object, or provide an Indexes object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Index Ids in the Indexes object will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Example
This example creates an Indexes object outside the application, which is used in the useIndexIds hook by reference. A newly-registered Index re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createIndexes, createStore} from 'tinybase';
import {useIndexIds} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
const App = () => <span>{JSON.stringify(useIndexIds(indexes))}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>[]</span>'
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
console.log(app.innerHTML);
// -> '<span>["bySpecies"]</span>'
Since
v4.1.0
useIndexes
The useIndexes hook is used to get a reference to an Indexes object from within a Provider component context.
useIndexes(id?: string): Indexes | undefined| Type | Description | |
|---|---|---|
id? | string | An optional |
| returns | Indexes | undefined | A reference to the |
A Provider component is used to wrap part of an application in a context. It can contain a default Indexes object (or a set of Indexes objects named by Id) that can be easily accessed without having to be passed down as props through every component.
The useIndexes hook lets you either get a reference to the default Indexes object (when called without a parameter), or one of the Indexes objects that are named by Id (when called with an Id parameter).
Examples
This example creates a Provider context into which a default Indexes object is provided. A component within it then uses the useIndexes hook to get a reference to the Indexes object again, without the need to have it passed as a prop.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createIndexes, createStore} from 'tinybase';
import {Provider, useIndexes} from 'tinybase/ui-react';
const App = ({indexes}) => (
<Provider indexes={indexes}>
<Pane />
</Provider>
);
const Pane = () => <span>{useIndexes().getListenerStats().sliceIds}</span>;
const indexes = createIndexes(createStore());
const app = document.createElement('div');
createRoot(app).render(<App indexes={indexes} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
This example creates a Provider context into which an Indexes object is provided, named by Id. A component within it then uses the useIndexes hook with that Id to get a reference to the Indexes object again, without the need to have it passed as a prop.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createIndexes, createStore} from 'tinybase';
import {Provider, useIndexes} from 'tinybase/ui-react';
const App = ({indexes}) => (
<Provider indexesById={{petStore: indexes}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useIndexes('petStore').getListenerStats().sliceIds}</span>
);
const indexes = createIndexes(createStore());
const app = document.createElement('div');
createRoot(app).render(<App indexes={indexes} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
Since
v1.0.0
useIndexesIds
The useIndexesIds hook is used to retrieve the Ids of all the named Indexes objects present in the current Provider component context.
useIndexesIds(): IdsExample
This example adds two named Indexes objects to a Provider context and an inner component accesses their Ids.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createIndexes, createStore} from 'tinybase';
import {
Provider,
useCreateIndexes,
useCreateStore,
useIndexesIds,
} from 'tinybase/ui-react';
const App = () => {
const store1 = useCreateStore(createStore);
const indexes1 = useCreateIndexes(store1, createIndexes);
const store2 = useCreateStore(createStore);
const indexes2 = useCreateIndexes(store2, createIndexes);
return (
<Provider indexesById={{indexes1, indexes2}}>
<Pane />
</Provider>
);
};
const Pane = () => <span>{JSON.stringify(useIndexesIds())}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["indexes1","indexes2"]</span>'
Since
v4.1.0
useIndexesOrIndexesById
The useIndexesOrIndexesById hook is used to get a reference to an Indexes object from within a Provider component context, or have it passed directly to this hook.
useIndexesOrIndexesById(indexesOrIndexesId?: IndexesOrIndexesId): Indexes | undefined| Type | Description | |
|---|---|---|
indexesOrIndexesId? | IndexesOrIndexesId | Either an |
| returns | Indexes | undefined | A reference to the |
This is mostly of use when you are developing a component that needs an Indexes object and which might have been passed in explicitly to the component or is to be picked up from the context by Id (a common pattern for Indexes-based components).
This hook is unlikely to be used often. For most situations, you will want to use the useIndexes hook.
Example
This example creates a Provider context into which a default Indexes object is provided. A component within it then uses the useIndexesOrIndexesById hook to get a reference to the Indexes object again, without the need to have it passed as a prop. Note however, that unlike the useIndexes hook example, this component would also work if you were to pass the Indexes object directly into it, making it more portable.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createIndexes, createStore} from 'tinybase';
import {Provider, useIndexesOrIndexesById} from 'tinybase/ui-react';
const App = ({indexes}) => (
<Provider indexes={indexes}>
<Pane />
</Provider>
);
const Pane = ({indexes}) => (
<span>
{JSON.stringify(useIndexesOrIndexesById(indexes).getIndexIds())}
</span>
);
const indexes = createIndexes(
createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
}),
).setIndexDefinition('bySpecies', 'pets', 'species');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App indexes={indexes} />);
console.log(app.innerHTML);
// -> '<span>["bySpecies"]</span>'
Since
v4.1.0
useProvideIndexes
The useProvideIndexes hook is used to add an Indexes object by Id to a Provider component, but imperatively from a component within it.
useProvideIndexes(
indexesId: string,
indexes: Indexes,
): void| Type | Description | |
|---|---|---|
indexesId | string | The |
indexes | Indexes | The |
| returns | void | This has no return value. |
Normally you will register an Indexes object by Id in a context by using the indexesById prop of the top-level Provider component. This hook, however, lets you dynamically add a new Indexes object to the context, from within a descendent component. This is useful for applications where the set of Indexes objects is not known at the time of the first render of the root Provider.
A Indexes object added to the Provider context in this way will be available to other components within the context (using the useIndexes hook and so on). If you use the same Id as an existing Indexes object registration, the new one will take priority over one provided by the indexesById prop.
Note that other components that consume an Indexes object registered like this should defend against it being undefined at first. On the first render, the other component will likely not yet have completed the registration. In the example below, we use the null-safe useIndexes('petIndexes')? to do this.
Example
This example creates a Provider context. A child component registers an Indexes object into it which is then consumable by a peer child component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createIndexes, createStore} from 'tinybase';
import {
Provider,
useCreateIndexes,
useCreateStore,
useIndexes,
useProvideIndexes,
} from 'tinybase/ui-react';
const App = () => (
<Provider>
<RegisterIndexes />
<ConsumeIndexes />
</Provider>
);
const RegisterIndexes = () => {
const store = useCreateStore(() =>
createStore().setCell('pets', 'fido', 'color', 'brown'),
);
const indexes = useCreateIndexes(store, (store) =>
createIndexes(store).setIndexDefinition(
'petsByColor',
'pets',
'color',
),
);
useProvideIndexes('petIndexes', indexes);
return null;
};
const ConsumeIndexes = () => (
<span>
{JSON.stringify(useIndexes('petIndexes')?.getSliceIds('petsByColor'))}
</span>
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
console.log(app.innerHTML);
// -> '<span>["brown"]</span>'
Since
v5.3.0
useSliceIds
The useSliceIds hook gets the list of Slice Ids in an Index, and registers a listener so that any changes to that result will cause a re-render.
useSliceIds(
indexId: string,
indexesOrIndexesId?: IndexesOrIndexesId,
): Ids| Type | Description | |
|---|---|---|
indexId | string | |
indexesOrIndexesId? | IndexesOrIndexesId | The |
| returns | Ids |
A Provider component is used to wrap part of an application in a context, and it can contain a default Indexes object or a set of Indexes objects named by Id. The useSliceIds hook lets you indicate which Indexes object to get data for: omit the optional final parameter for the default context Indexes object, provide an Id for a named context Indexes object, or provide a Indexes object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Slice Ids will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates an Indexes object outside the application, which is used in the useSliceIds hook by reference. A change to the Slice Ids re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createIndexes, createStore} from 'tinybase';
import {useSliceIds} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const App = () => (
<span>{JSON.stringify(useSliceIds('bySpecies', indexes))}</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["dog","cat"]</span>'
store.setRow('pets', 'lowly', {species: 'worm'});
console.log(app.innerHTML);
// -> '<span>["dog","cat","worm"]</span>'
This example creates a Provider context into which a default Indexes object is provided. A component within it then uses the useSliceIds hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createIndexes, createStore} from 'tinybase';
import {Provider, useSliceIds} from 'tinybase/ui-react';
const App = ({indexes}) => (
<Provider indexes={indexes}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useSliceIds('bySpecies'))}</span>;
const indexes = createIndexes(
createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
}),
).setIndexDefinition('bySpecies', 'pets', 'species');
const app = document.createElement('div');
createRoot(app).render(<App indexes={indexes} />);
console.log(app.innerHTML);
// -> '<span>["dog","cat"]</span>'
This example creates a Provider context into which a default Indexes object is provided. A component within it then uses the useSliceIds hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createIndexes, createStore} from 'tinybase';
import {Provider, useSliceIds} from 'tinybase/ui-react';
const App = ({indexes}) => (
<Provider indexesById={{petIndexes: indexes}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useSliceIds('bySpecies', 'petIndexes'))}</span>
);
const indexes = createIndexes(
createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
}),
).setIndexDefinition('bySpecies', 'pets', 'species');
const app = document.createElement('div');
createRoot(app).render(<App indexes={indexes} />);
console.log(app.innerHTML);
// -> '<span>["dog","cat"]</span>'
Since
v1.0.0
useSliceIdsListener
The useSliceIdsListener hook registers a listener function with the Indexes object that will be called whenever the Slice Ids in an Index change.
useSliceIdsListener(
indexId: IdOrNull,
listener: SliceIdsListener,
listenerDeps?: DependencyList,
indexesOrIndexesId?: IndexesOrIndexesId,
): void| Type | Description | |
|---|---|---|
indexId | IdOrNull | |
listener | SliceIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
indexesOrIndexesId? | IndexesOrIndexesId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useSliceIds hook).
You can either listen to a single Index (by specifying the Index Id as the method's first parameter), or changes to any Index (by providing a null wildcard).
Unlike the addSliceIdsListener method, which returns a listener Id and requires you to remove it manually, the useSliceIdsListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Indexes object will be deleted.
Example
This example uses the useSliceIdsListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Indexes object.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createIndexes, createStore} from 'tinybase';
import {Provider, useSliceIdsListener} from 'tinybase/ui-react';
const App = ({indexes}) => (
<Provider indexes={indexes}>
<Pane />
</Provider>
);
const Pane = () => {
useSliceIdsListener('bySpecies', () => console.log('Slice Ids changed'));
return <span>App</span>;
};
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App indexes={indexes} />);
console.log(indexes.getListenerStats().sliceIds);
// -> 1
store.setRow('pets', 'lowly', {species: 'worm'});
// -> 'Slice Ids changed'
root.unmount();
console.log(indexes.getListenerStats().sliceIds);
// -> 0
Since
v1.0.0
Metrics hooks
useCreateMetrics
The useCreateMetrics hook is used to create a Metrics object within a React application with convenient memoization.
useCreateMetrics(
store: undefined | Store,
create: (store: Store) => Metrics,
createDeps?: DependencyList,
): Metrics | undefined| Type | Description | |
|---|---|---|
store | undefined | Store | A reference to the |
create | (store: Store) => Metrics | A function for performing the creation steps of the |
createDeps? | DependencyList | An optional array of dependencies for the |
| returns | Metrics | undefined | A reference to the |
It is possible to create a Metrics object outside of the React app with the regular createMetrics function and pass it in, but you may prefer to create it within the app, perhaps inside the top-level component. To prevent a new Metrics object being created every time the app renders or re-renders, since v5.0 this hook performs the creation in an effect. As a result it will return undefined on the brief first render (or if the Store is not yet defined), which you should defend against.
If your create function contains other dependencies, the changing of which should also cause the Metrics object to be recreated, you can provide them in an array in the optional second parameter, just as you would for any React hook with dependencies.
This hook ensures the Metrics object is destroyed whenever a new one is created or the component is unmounted.
Examples
This example creates a Metrics object at the top level of a React application. Even though the App component is rendered twice, the Metrics object creation only occurs once by default.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createMetrics, createStore} from 'tinybase';
import {useCreateMetrics, useCreateStore} from 'tinybase/ui-react';
const App = () => {
const store = useCreateStore(() =>
createStore().setTable('species', {dog: {price: 5}, cat: {price: 4}}),
);
const metrics = useCreateMetrics(store, (store) => {
console.log('Metrics created');
return createMetrics(store).setMetricDefinition(
'speciesCount',
'species',
);
});
return <span>{metrics?.getMetric('speciesCount')}</span>;
};
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
// -> 'Metrics created'
root.render(<App />);
// No second Metrics creation
console.log(app.innerHTML);
// -> '<span>2</span>'
This example creates a Metrics object at the top level of a React application. The App component is rendered twice, each with a different top-level prop. The useCreateMetrics hook takes the tableToCount prop as a dependency, and so the Metrics object is created again on the second render.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createMetrics, createStore} from 'tinybase';
import {useCreateMetrics, useCreateStore} from 'tinybase/ui-react';
const App = ({tableToCount}) => {
const store = useCreateStore(() =>
createStore()
.setTable('pets', {fido: {species: 'dog'}})
.setTable('species', {dog: {price: 5}, cat: {price: 4}}),
);
const metrics = useCreateMetrics(
store,
(store) => {
console.log(`Count created for ${tableToCount} table`);
return createMetrics(store).setMetricDefinition(
'tableCount',
tableToCount,
);
},
[tableToCount],
);
return <span>{metrics?.getMetric('tableCount')}</span>;
};
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App tableToCount="pets" />);
// -> 'Count created for pets table'
console.log(app.innerHTML);
// -> '<span>1</span>'
root.render(<App tableToCount="species" />);
// -> 'Count created for species table'
console.log(app.innerHTML);
// -> '<span>2</span>'
Since
v1.0.0
useMetric
The useMetric hook gets the current value of a Metric, and registers a listener so that any changes to that result will cause a re-render.
useMetric(
metricId: string,
metricsOrMetricsId?: MetricsOrMetricsId,
): number | undefined| Type | Description | |
|---|---|---|
metricId | string | |
metricsOrMetricsId? | MetricsOrMetricsId | The |
| returns | number | undefined | The numeric value of the |
A Provider component is used to wrap part of an application in a context, and it can contain a default Metrics object or a set of Metrics objects named by Id. The useMetric hook lets you indicate which Metrics object to get data for: omit the optional final parameter for the default context Metrics object, provide an Id for a named context Metrics object, or provide a Metrics object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Metric will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Metrics object outside the application, which is used in the useMetric hook by reference. A change to the Metric re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createMetrics, createStore} from 'tinybase';
import {useMetric} from 'tinybase/ui-react';
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
const App = () => <span>{useMetric('highestPrice', metrics)}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>5</span>'
store.setCell('species', 'horse', 'price', 20);
console.log(app.innerHTML);
// -> '<span>20</span>'
This example creates a Provider context into which a default Metrics object is provided. A component within it then uses the useMetric hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createMetrics, createStore} from 'tinybase';
import {Provider, useMetric} from 'tinybase/ui-react';
const App = ({metrics}) => (
<Provider metrics={metrics}>
<Pane />
</Provider>
);
const Pane = () => <span>{useMetric('highestPrice')}</span>;
const metrics = createMetrics(
createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
}),
).setMetricDefinition('highestPrice', 'species', 'max', 'price');
const app = document.createElement('div');
createRoot(app).render(<App metrics={metrics} />);
console.log(app.innerHTML);
// -> '<span>5</span>'
This example creates a Provider context into which a default Metrics object is provided. A component within it then uses the useMetric hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createMetrics, createStore} from 'tinybase';
import {Provider, useMetric} from 'tinybase/ui-react';
const App = ({metrics}) => (
<Provider metricsById={{petMetrics: metrics}}>
<Pane />
</Provider>
);
const Pane = () => <span>{useMetric('highestPrice', 'petMetrics')}</span>;
const metrics = createMetrics(
createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
}),
).setMetricDefinition('highestPrice', 'species', 'max', 'price');
const app = document.createElement('div');
createRoot(app).render(<App metrics={metrics} />);
console.log(app.innerHTML);
// -> '<span>5</span>'
Since
v1.0.0
useMetricIds
The useMetricIds hook gets an array of the Metric Ids registered with a Metrics object, and registers a listener so that any changes to that result will cause a re-render.
useMetricIds(metricsOrMetricsId?: MetricsOrMetricsId): Ids| Type | Description | |
|---|---|---|
metricsOrMetricsId? | MetricsOrMetricsId | The |
| returns | Ids |
A Provider component is used to wrap part of an application in a context, and it can contain a default Metrics object or a set of Metrics objects named by Id. The useMetricIds hook lets you indicate which Metrics object to get data for: omit the optional final parameter for the default context Metrics object, provide an Id for a named context Metrics object, or provide a Metrics object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Metric Ids in the Metrics object will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Example
This example creates an Metrics object outside the application, which is used in the useMetricIds hook by reference. A newly-registered Metric re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createMetrics, createStore} from 'tinybase';
import {useMetricIds} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const metrics = createMetrics(store);
const App = () => <span>{JSON.stringify(useMetricIds(metrics))}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>[]</span>'
const addMetricDefinition = () =>
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
addMetricDefinition();
console.log(app.innerHTML);
// -> '<span>["highestPrice"]</span>'
Since
v4.1.0
useMetricListener
The useMetricListener hook registers a listener function with the Metrics object that will be called whenever the value of a specified Metric changes.
useMetricListener(
metricId: IdOrNull,
listener: MetricListener,
listenerDeps?: DependencyList,
metricsOrMetricsId?: MetricsOrMetricsId,
): void| Type | Description | |
|---|---|---|
metricId | IdOrNull | |
listener | MetricListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
metricsOrMetricsId? | MetricsOrMetricsId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useMetric hook).
You can either listen to a single Metric (by specifying the Metric Id as the method's first parameter), or changes to any Metric (by providing a null wildcard).
Unlike the addMetricListener method, which returns a listener Id and requires you to remove it manually, the useMetricListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Metrics object, will be deleted.
Example
This example uses the useMetricListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Metrics object.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createMetrics, createStore} from 'tinybase';
import {Provider, useMetricListener} from 'tinybase/ui-react';
const App = ({metrics}) => (
<Provider metrics={metrics}>
<Pane />
</Provider>
);
const Pane = () => {
useMetricListener('highestPrice', () => console.log('Metric changed'));
return <span>App</span>;
};
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App metrics={metrics} />);
console.log(metrics.getListenerStats().metric);
// -> 1
store.setCell('species', 'horse', 'price', 20);
// -> 'Metric changed'
root.unmount();
console.log(metrics.getListenerStats().metric);
// -> 0
Since
v1.0.0
useMetrics
The useMetrics hook is used to get a reference to a Metrics object from within a Provider component context.
useMetrics(id?: string): Metrics | undefined| Type | Description | |
|---|---|---|
id? | string | An optional |
| returns | Metrics | undefined | A reference to the |
A Provider component is used to wrap part of an application in a context. It can contain a default Metrics object (or a set of Metrics objects named by Id) that can be easily accessed without having to be passed down as props through every component.
The useMetrics hook lets you either get a reference to the default Metrics object (when called without a parameter), or one of the Metrics objects that are named by Id (when called with an Id parameter).
Examples
This example creates a Provider context into which a default Metrics object is provided. A component within it then uses the useMetrics hook to get a reference to the Metrics object again, without the need to have it passed as a prop.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createMetrics, createStore} from 'tinybase';
import {Provider, useMetrics} from 'tinybase/ui-react';
const App = ({metrics}) => (
<Provider metrics={metrics}>
<Pane />
</Provider>
);
const Pane = () => <span>{useMetrics().getListenerStats().metric}</span>;
const metrics = createMetrics(createStore());
const app = document.createElement('div');
createRoot(app).render(<App metrics={metrics} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
This example creates a Provider context into which a Metrics object is provided, named by Id. A component within it then uses the useMetrics hook with that Id to get a reference to the Metrics object again, without the need to have it passed as a prop.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createMetrics, createStore} from 'tinybase';
import {Provider, useMetrics} from 'tinybase/ui-react';
const App = ({metrics}) => (
<Provider metricsById={{petStore: metrics}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useMetrics('petStore').getListenerStats().metric}</span>
);
const metrics = createMetrics(createStore());
const app = document.createElement('div');
createRoot(app).render(<App metrics={metrics} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
Since
v1.0.0
useMetricsIds
The useMetricsIds hook is used to retrieve the Ids of all the named Metrics objects present in the current Provider component context.
useMetricsIds(): IdsExample
This example adds two named Metrics objects to a Provider context and an inner component accesses their Ids.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createMetrics, createStore} from 'tinybase';
import {
Provider,
useCreateMetrics,
useCreateStore,
useMetricsIds,
} from 'tinybase/ui-react';
const App = () => {
const store1 = useCreateStore(createStore);
const metrics1 = useCreateMetrics(store1, createMetrics);
const store2 = useCreateStore(createStore);
const metrics2 = useCreateMetrics(store2, createMetrics);
return (
<Provider metricsById={{metrics1, metrics2}}>
<Pane />
</Provider>
);
};
const Pane = () => <span>{JSON.stringify(useMetricsIds())}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["metrics1","metrics2"]</span>'
Since
v4.1.0
useMetricsOrMetricsById
The useMetricsOrMetricsById hook is used to get a reference to a Metrics object from within a Provider component context, or have it passed directly to this hook.
useMetricsOrMetricsById(metricsOrMetricsId?: MetricsOrMetricsId): Metrics | undefined| Type | Description | |
|---|---|---|
metricsOrMetricsId? | MetricsOrMetricsId | Either an |
| returns | Metrics | undefined | A reference to the |
This is mostly of use when you are developing a component that needs a Metrics object and which might have been passed in explicitly to the component or is to be picked up from the context by Id (a common pattern for Metrics-based components).
This hook is unlikely to be used often. For most situations, you will want to use the useMetrics hook.
Example
This example creates a Provider context into which a default Metrics object is provided. A component within it then uses the useMetricsOrMetricsById hook to get a reference to the Metrics object again, without the need to have it passed as a prop. Note however, that unlike the useMetrics hook example, this component would also work if you were to pass the Metrics object directly into it, making it more portable.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createMetrics, createStore} from 'tinybase';
import {Provider, useMetricsOrMetricsById} from 'tinybase/ui-react';
const App = ({metrics}) => (
<Provider metrics={metrics}>
<Pane />
</Provider>
);
const Pane = ({metrics}) => (
<span>
{JSON.stringify(useMetricsOrMetricsById(metrics).getMetricIds())}
</span>
);
const metrics = createMetrics(
createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
}),
).setMetricDefinition('highestPrice', 'species', 'max', 'price');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App metrics={metrics} />);
console.log(app.innerHTML);
// -> '<span>["highestPrice"]</span>'
Since
v4.1.0
useProvideMetrics
The useProvideMetrics hook is used to add a Metrics object by Id to a Provider component, but imperatively from a component within it.
useProvideMetrics(
metricsId: string,
metrics: Metrics,
): void| Type | Description | |
|---|---|---|
metricsId | string | The |
metrics | Metrics | The |
| returns | void | This has no return value. |
Normally you will register a Metrics object by Id in a context by using the metricsById prop of the top-level Provider component. This hook, however, lets you dynamically add a new Metrics object to the context, from within a descendent component. This is useful for applications where the set of Metrics objects is not known at the time of the first render of the root Provider.
A Metrics object added to the Provider context in this way will be available to other components within the context (using the useMetrics hook and so on). If you use the same Id as an existing Metrics object registration, the new one will take priority over one provided by the metricsById prop.
Note that other components that consume a Metrics object registered like this should defend against it being undefined at first. On the first render, the other component will likely not yet have completed the registration. In the example below, we use the null-safe useMetrics('petMetrics')? to do this.
Example
This example creates a Provider context. A child component registers a Metrics object into it which is then consumable by a peer child component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createMetrics, createStore} from 'tinybase';
import {
Provider,
useCreateMetrics,
useCreateStore,
useMetrics,
useProvideMetrics,
} from 'tinybase/ui-react';
const App = () => (
<Provider>
<RegisterMetrics />
<ConsumeMetrics />
</Provider>
);
const RegisterMetrics = () => {
const store = useCreateStore(() =>
createStore().setCell('pets', 'fido', 'color', 'brown'),
);
const metrics = useCreateMetrics(store, (store) =>
createMetrics(store).setMetricDefinition('petCount', 'pets', 'count'),
);
useProvideMetrics('petMetrics', metrics);
return null;
};
const ConsumeMetrics = () => (
<span>{useMetrics('petMetrics')?.getMetric('petCount')}</span>
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
console.log(app.innerHTML);
// -> '<span>1</span>'
Since
v5.3.0
Persister hooks
useCreatePersister
The useCreatePersister hook is used to create a Persister within a React application along with convenient memoization and callbacks.
useCreatePersister<Persist, PersisterOrUndefined>(
store: undefined | PersistedStore<Persist>,
create: (store: PersistedStore<Persist>) => PersisterOrUndefined | Promise<PersisterOrUndefined>,
createDeps?: DependencyList,
then?: (persister: Persister<Persist>) => Promise<any>,
thenDeps?: DependencyList,
destroy?: (persister: Persister<Persist>) => void,
destroyDeps?: DependencyList,
): PersisterOrUndefined| Type | Description | |
|---|---|---|
store | undefined | PersistedStore<Persist> | A reference to the |
create | (store: PersistedStore<Persist>) => PersisterOrUndefined | Promise<PersisterOrUndefined> | A (possibly asynchronous) function for performing the creation steps of the |
createDeps? | DependencyList | An optional array of dependencies for the |
then? | (persister: Persister<Persist>) => Promise<any> | An optional callback for performing asynchronous post-creation steps on the |
thenDeps? | DependencyList | An optional array of dependencies for the |
destroy? | (persister: Persister<Persist>) => void | An optional callback whenever the |
destroyDeps? | DependencyList | An optional array of dependencies for the |
| returns | PersisterOrUndefined | A reference to the |
It is possible to create a Persister outside of the React app with the regular createPersister function and pass it in, but you may prefer to create it within the app, perhaps inside the top-level component. To prevent a new Persister being created every time the app renders or re-renders, since v5.0 the this hook performs the creation in an effect.
If your create function (the second parameter to the hook) contains dependencies, the changing of which should cause the Persister to be recreated, you can provide them in an array in the third parameter, just as you would for any React hook with dependencies. The Store passed in as the first parameter of this hook is used as a dependency by default.
A second callback, called then, can be provided as the fourth parameter. This is called after the creation, and, importantly, can be asynchronous, so that you can configure the Persister with the startAutoLoad method and startAutoSave method, for example. If this callback contains dependencies, the changing of which should cause the Persister to be reconfigured, you can provide them in an array in the fifth parameter. The Persister itself is used as a dependency by default.
See the note below about possible future deprecation of the then callback, however.
Since v4.3.0, the create function can return undefined, meaning that you can enable or disable persistence conditionally within this hook. This is useful for applications which might turn on or off their cloud persistence or collaboration features. This hook can return undefined if the Store is not yet defined, which you should defend against.
Since v4.3.19, a destroy function can be provided which will be called after an old Persister is destroyed due to a change in the createDeps dependencies that causes a new one to be created. Use this to clean up any underlying storage objects that you set up during the then function, for example. If this callback itself contains additional dependencies, you can provide them in an array in the seventh parameter.
Since v5.2, the create function can be asynchronous, which now makes it a suitable place to call the Persister's startAutoLoad and startAutoSave methods. At some major version in the future, the then parameter will be removed, since that only really existed to perform such asynchronous initial tasks.
This hook ensures the Persister object is destroyed whenever a new one is created or the component is unmounted.
Examples
This example creates a Persister at the top level of a React application. Even though the App component is rendered twice, the Persister creation only occurs once by default.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {
useCreatePersister,
useCreateStore,
useTables,
} from 'tinybase/ui-react';
const App = () => {
const store = useCreateStore(createStore);
useCreatePersister(
store,
(store) => {
console.log('Persister created');
return createSessionPersister(store, 'pets');
},
[],
async (persister) => {
await persister.startAutoLoad();
await persister.startAutoSave();
},
);
return <span>{JSON.stringify(useTables(store))}</span>;
};
sessionStorage.setItem(
'pets',
'[{"pets":{"fido":{"species":"dog"}}}, {}]',
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
// -> 'Persister created'
// ...
root.render(<App />);
// No second Persister creation
console.log(app.innerHTML);
// -> '<span>{\"pets\":{\"fido\":{\"species\":\"dog\"}}}</span>'
root.unmount();
This example creates a Persister at the top level of a React application. The App component is rendered twice, each with a different top-level prop. The useCreatePersister hook takes the sessionKey prop as a dependency, and so the Persister object is created again on the second render. The first is destroyed and the destroy parameter is called for it.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {
useCreatePersister,
useCreateStore,
useTables,
} from 'tinybase/ui-react';
const App = ({sessionKey}) => {
const store = useCreateStore(createStore);
useCreatePersister(
store,
(store) => {
console.log(`Persister created for session key ${sessionKey}`);
return createSessionPersister(store, sessionKey);
},
[sessionKey],
async (persister) => {
await persister.startAutoLoad();
},
[],
(persister) =>
console.log(
`Persister destroyed for session key ${persister.getStorageName()}`,
),
);
return <span>{JSON.stringify(useTables(store))}</span>;
};
sessionStorage.setItem(
'fidoStore',
'[{"pets":{"fido":{"species":"dog"}}}, {}]',
);
sessionStorage.setItem(
'cujoStore',
'[{"pets":{"cujo":{"species":"dog"}}}, {}]',
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App sessionKey="fidoStore" />);
// -> 'Persister created for session key fidoStore'
// ...
console.log(app.innerHTML);
// -> '<span>{\"pets\":{\"fido\":{\"species\":\"dog\"}}}</span>'
root.render(<App sessionKey="cujoStore" />);
// -> 'Persister created for session key cujoStore'
// -> 'Persister destroyed for session key fidoStore'
// ...
console.log(app.innerHTML);
// -> '<span>{\"pets\":{\"cujo\":{\"species\":\"dog\"}}}</span>'
root.unmount();
// -> 'Persister destroyed for session key cujoStore'
Since
v1.0.0
usePersister
The usePersister hook is used to get a reference to a Persister object from within a Provider component context.
usePersister(id?: string): AnyPersister | undefined| Type | Description | |
|---|---|---|
id? | string | An optional |
| returns | AnyPersister | undefined | A reference to the |
A Provider component is used to wrap part of an application in a context. It can contain a default Persister object (or a set of Persister objects named by Id) that can be easily accessed without having to be passed down as props through every component.
The usePersister hook lets you either get a reference to the default Persister object (when called without a parameter), or one of the Persister objects that are named by Id (when called with an Id parameter).
Examples
This example creates a Provider context into which a default Persister object is provided. A component within it then uses the usePersister hook to get a reference to the Persister object again, without the need to have it passed as a prop.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {Provider, usePersister} from 'tinybase/ui-react';
const App = ({persister}) => (
<Provider persister={persister}>
<Pane />
</Provider>
);
const Pane = () => <span>{usePersister().getStatus()}</span>;
const persister = createSessionPersister(createStore(), 'pets');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App persister={persister} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
This example creates a Provider context into which a Persister object is provided, named by Id. A component within it then uses the usePersister hook with that Id to get a reference to the Persister object again, without the need to have it passed as a prop.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {Provider, usePersister} from 'tinybase/ui-react';
const App = ({persister}) => (
<Provider persistersById={{petPersister: persister}}>
<Pane />
</Provider>
);
const Pane = () => <span>{usePersister('petPersister').getStatus()}</span>;
const persister = createSessionPersister(createStore(), 'pets');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App persister={persister} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
Since
v5.3.0
usePersisterIds
The usePersisterIds hook is used to retrieve the Ids of all the named Persister objects present in the current Provider component context.
usePersisterIds(): IdsExample
This example adds two named Persister objects to a Provider context and an inner component accesses their Ids.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {
Provider,
useCreatePersister,
useCreateStore,
usePersisterIds,
} from 'tinybase/ui-react';
const App = () => {
const store1 = useCreateStore(createStore);
const persister1 = useCreatePersister(store1, (store1) =>
createSessionPersister(store1, 'pets1'),
);
const store2 = useCreateStore(createStore);
const persister2 = useCreatePersister(store2, (store2) =>
createSessionPersister(store2, 'pets2'),
);
return (
<Provider persistersById={{persister1, persister2}}>
<Pane />
</Provider>
);
};
const Pane = () => <span>{JSON.stringify(usePersisterIds())}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
// ...
console.log(app.innerHTML);
// -> '<span>["persister1","persister2"]</span>'
Since
v5.3.0
usePersisterOrPersisterById
The usePersisterOrPersisterById hook is used to get a reference to a Persister object from within a Provider component context, or have it passed directly to this hook.
usePersisterOrPersisterById(persisterOrPersisterId?: PersisterOrPersisterId): AnyPersister | undefined| Type | Description | |
|---|---|---|
persisterOrPersisterId? | PersisterOrPersisterId | Either an |
| returns | AnyPersister | undefined | A reference to the |
This is mostly of use when you are developing a component that needs a Persister object and which might have been passed in explicitly to the component or is to be picked up from the context by Id (a common pattern for Persister-based components).
This is unlikely to be used often. For most situations, you will want to use the usePersister hook.
Example
This example creates a Provider context into which a default Persister object is provided. A component within it then uses the usePersisterOrPersisterById hook to get a reference to the Persister object again, without the need to have it passed as a prop. Note however, that unlike the usePersister hook example, this component would also work if you were to pass the Persister object directly into it, making it more portable.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {Provider, usePersisterOrPersisterById} from 'tinybase/ui-react';
const App = ({persister}) => (
<Provider persister={persister}>
<Pane />
</Provider>
);
const Pane = ({persister}) => (
<span>{usePersisterOrPersisterById(persister).getStatus()}</span>
);
const persister = createSessionPersister(createStore(), 'pets');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App persister={persister} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
Since
v5.3.0
usePersisterStatus
The usePersisterStatus hook returns a the status of a Persister, and registers a listener so that any changes to it will cause a re-render.
usePersisterStatus(persisterOrPersisterId?: PersisterOrPersisterId): Status| Type | Description | |
|---|---|---|
persisterOrPersisterId? | PersisterOrPersisterId | The |
| returns | Status | The status of the |
A Provider component is used to wrap part of an application in a context, and it can contain a default Persister or a set of Persister objects named by Id. The usePersisterStatus hook lets you indicate which Persister to get data for: omit the optional parameter for the default context Persister, provide an Id for a named context Persister, or provide a Persister explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Persister status will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Persister outside the application, which is used in the usePersisterStatus hook by reference. A change to the status of the Persister re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {usePersisterStatus} from 'tinybase/ui-react';
const persister = createSessionPersister(createStore(), 'pets');
const App = () => <span>{usePersisterStatus(persister)}</span>;
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
console.log(app.innerHTML);
// -> '<span>0</span>'
This example creates a Provider context into which a default Persister is provided. A component within it then uses the usePersisterStatus hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {Provider, usePersisterStatus} from 'tinybase/ui-react';
const App = ({persister}) => (
<Provider persister={persister}>
<Pane />
</Provider>
);
const Pane = () => <span>{usePersisterStatus()}</span>;
const persister = createSessionPersister(createStore(), 'pets');
const app = document.createElement('div');
createRoot(app).render(<App persister={persister} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
This example creates a Provider context into which a Persister is provided, named by Id. A component within it then uses the usePersisterStatus hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {Provider, usePersisterStatus} from 'tinybase/ui-react';
const App = ({persister}) => (
<Provider persistersById={{petPersister: persister}}>
<Pane />
</Provider>
);
const Pane = () => <span>{usePersisterStatus('petPersister')}</span>;
const persister = createSessionPersister(createStore(), 'pets');
const app = document.createElement('div');
createRoot(app).render(<App persister={persister} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
Since
v5.3.0
usePersisterStatusListener
The usePersisterStatusListener hook registers a listener function with the Persister that will be called when its status changes.
usePersisterStatusListener(
listener: StatusListener<StoreOrMergeableStore>,
listenerDeps?: DependencyList,
persisterOrPersisterId?: PersisterOrPersisterId,
): void| Type | Description | |
|---|---|---|
listener | StatusListener<StoreOrMergeableStore> | The function that will be called whenever the status of the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
persisterOrPersisterId? | PersisterOrPersisterId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the usePersisterStatus hook).
Unlike the addStatusListener method, which returns a listener Id and requires you to remove it manually, the usePersisterStatusListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Persister will be deleted.
Example
This example uses the usePersisterStatusListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Persister.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {Provider, usePersisterStatusListener} from 'tinybase/ui-react';
const App = ({persister}) => (
<Provider persister={persister}>
<Pane />
</Provider>
);
const Pane = () => {
usePersisterStatusListener((persister, status) =>
console.log('Persister status changed: ' + status),
);
return <span>App</span>;
};
const persister = createSessionPersister(createStore(), 'pets');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App persister={persister} />);
persister.load();
// -> 'Persister status changed: 1'
// ...
// -> 'Persister status changed: 0'
persister.save();
// -> 'Persister status changed: 2'
// ...
// -> 'Persister status changed: 0'
Since
v5.3.0
useProvidePersister
The useProvidePersister hook is used to add a Persister object by Id to a Provider component, but imperatively from a component within it.
useProvidePersister(
persisterId: string,
persister: AnyPersister,
): void| Type | Description | |
|---|---|---|
persisterId | string | The |
persister | AnyPersister | The |
| returns | void | This has no return value. |
Normally you will register a Persister object by Id in a context by using the persistersById prop of the top-level Provider component. This hook, however, lets you dynamically add a new Persister object to the context, from within a component. This is useful for applications where the set of Persister objects is not known at the time of the first render of the root Provider.
A Persister object added to the Provider context in this way will be available to other components within the context (using the usePersister hook and so on). If you use the same Id as an existing Persister object registration, the new one will take priority over one provided by the persistersById prop.
Note that other components that consume a Persister object registered like this should defend against it being undefined at first. On the first render, the other component will likely not yet have completed the registration. In the example below, we use the null-safe usePersister('petPersister')? to do this.
Example
This example creates a Provider context. A child component registers a Persister object into it which is then consumable by a peer child component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {
Provider,
useCreatePersister,
useCreateStore,
usePersister,
useProvidePersister,
} from 'tinybase/ui-react';
const App = () => (
<Provider>
<RegisterPersister />
<ConsumePersister />
</Provider>
);
const RegisterPersister = () => {
const store = useCreateStore(() =>
createStore().setCell('pets', 'fido', 'color', 'brown'),
);
const persister = useCreatePersister(store, (store) =>
createSessionPersister(store, 'pets'),
);
useProvidePersister('petPersister', persister);
return null;
};
const ConsumePersister = () => (
<span>{usePersister('petPersister')?.getStatus()}</span>
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
// ...
console.log(app.innerHTML);
// -> '<span>0</span>'
Since
v5.3.0
Queries hooks
useResultTable
The useResultTable hook returns an object containing the entire data of the ResultTable of the given query, and registers a listener so that any changes to that result will cause a re-render.
useResultTable(
queryId: string,
queriesOrQueriesId?: QueriesOrQueriesId,
): Table| Type | Description | |
|---|---|---|
queryId | string | The |
queriesOrQueriesId? | QueriesOrQueriesId | The |
| returns | Table | An object containing the entire data of the |
A Provider component is used to wrap part of an application in a context, and it can contain a default Queries object or a set of Queries objects named by Id. The useResultTable hook lets you indicate which Queries object to get data for: omit the final optional final parameter for the default context Queries object, provide an Id for a named context Queries object, or provide a Queries object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the query result will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Queries object outside the application, which is used in the useTable hook by reference. A change to the data in the query re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {useResultTable} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const App = () => (
<span>{JSON.stringify(useResultTable('dogColors', queries))}</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>{"fido":{"color":"brown"},"cujo":{"color":"black"}}</span>'
store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<span>{"fido":{"color":"walnut"},"cujo":{"color":"black"}}</span>'
This example creates a Provider context into which a default Queries object is provided. A component within it then uses the useResultTable hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {Provider, useResultTable} from 'tinybase/ui-react';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useResultTable('dogColors'))}</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>{"fido":{"color":"brown"},"cujo":{"color":"black"}}</span>'
This example creates a Provider context into which a Queries object is provided, named by Id. A component within it then uses the useResultTable hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {Provider, useResultTable} from 'tinybase/ui-react';
const App = ({queries}) => (
<Provider queriesById={{petQueries: queries}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useResultTable('dogColors', 'petQueries'))}</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>{"fido":{"color":"brown"},"cujo":{"color":"black"}}</span>'
Since
v2.0.0
useResultTableCellIds
The useResultTableCellIds hook returns the Ids of every Cell used across the whole ResultTable of the given query, and registers a listener so that any changes to those Ids will cause a re-render.
useResultTableCellIds(
queryId: string,
queriesOrQueriesId?: QueriesOrQueriesId,
): Ids| Type | Description | |
|---|---|---|
queryId | string | The |
queriesOrQueriesId? | QueriesOrQueriesId | The |
| returns | Ids | An array of the |
A Provider component is used to wrap part of an application in a context, and it can contain a default Queries object or a set of Queries objects named by Id. The useResultTableCellIds hook lets you indicate which Queries object to get data for: omit the final optional final parameter for the default context Queries object, provide an Id for a named context Queries object, or provide a Queries object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the result Cell Ids will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Queries object outside the application, which is used in the useResultTableCellIds hook by reference. A change to the data in the query re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {useResultTableCellIds} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColorsAndLegs',
'pets',
({select, where}) => {
select('color');
select('legs');
where('species', 'dog');
},
);
const App = () => (
<span>
{JSON.stringify(useResultTableCellIds('dogColorsAndLegs', queries))}
</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["color"]</span>'
store.setCell('pets', 'cujo', 'legs', 4);
console.log(app.innerHTML);
// -> '<span>["color","legs"]</span>'
This example creates a Provider context into which a default Queries object is provided. A component within it then uses the useResultTableCellIds hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {Provider, useResultTableCellIds} from 'tinybase/ui-react';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useResultTableCellIds('dogColorsAndLegs'))}</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black', legs: 4},
}),
).setQueryDefinition('dogColorsAndLegs', 'pets', ({select, where}) => {
select('color');
select('legs');
where('species', 'dog');
});
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>["color","legs"]</span>'
This example creates a Provider context into which a Queries object is provided, named by Id. A component within it then uses the useResultTableCellIds hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {Provider, useResultTableCellIds} from 'tinybase/ui-react';
const App = ({queries}) => (
<Provider queriesById={{petQueries: queries}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{JSON.stringify(
useResultTableCellIds('dogColorsAndLegs', 'petQueries'),
)}
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black', legs: 4},
}),
).setQueryDefinition('dogColorsAndLegs', 'pets', ({select, where}) => {
select('color');
select('legs');
where('species', 'dog');
});
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>["color","legs"]</span>'
Since
v4.1.0
useResultTableCellIdsListener
The useResultTableCellIdsListener hook registers a listener function with a Queries object that will be called whenever the Cell Ids that appear anywhere in a ResultTable change.
useResultTableCellIdsListener(
queryId: IdOrNull,
listener: ResultTableCellIdsListener,
listenerDeps?: DependencyList,
queriesOrQueriesId?: QueriesOrQueriesId,
): void| Type | Description | |
|---|---|---|
queryId | IdOrNull | The |
listener | ResultTableCellIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
queriesOrQueriesId? | QueriesOrQueriesId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useResultTableCellIds hook).
You can either listen to a single ResultTable (by specifying a query Id as the method's first parameter) or changes to any ResultTable (by providing a null wildcard).
Unlike the addResultTableCellIdsListener method, which returns a listener Id and requires you to remove it manually, the useResultTableCellIdsListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Queries object will be deleted.
Example
This example uses the useResultTableCellIdsListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Queries object.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {Provider, useResultTableCellIdsListener} from 'tinybase/ui-react';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => {
useResultTableCellIdsListener('petColorsAndLegs', () =>
console.log('Result Cell Ids changed'),
);
return <span>App</span>;
};
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColorsAndLegs',
'pets',
({select}) => {
select('color');
select('legs');
},
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App queries={queries} />);
console.log(queries.getListenerStats().tableCellIds);
// -> 1
store.setCell('pets', 'cujo', 'legs', 4);
// -> 'Result Cell Ids changed'
root.unmount();
console.log(queries.getListenerStats().tableCellIds);
// -> 0
Since
v4.1.0
useResultTableListener
The useResultTableListener hook registers a listener function with a Queries object that will be called whenever data in a ResultTable changes.
useResultTableListener(
queryId: IdOrNull,
listener: ResultTableListener,
listenerDeps?: DependencyList,
queriesOrQueriesId?: QueriesOrQueriesId,
): void| Type | Description | |
|---|---|---|
queryId | IdOrNull | The |
listener | ResultTableListener | The function that will be called whenever data in the matching |
listenerDeps? | DependencyList | An optional array of dependencies for the |
queriesOrQueriesId? | QueriesOrQueriesId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useResultTable hook).
You can either listen to a single ResultTable (by specifying a query Id as the method's first parameter) or changes to any ResultTable (by providing a null wildcard).
Unlike the addResultTableListener method, which returns a listener Id and requires you to remove it manually, the useResultTableListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Queries object will be deleted.
Example
This example uses the useResultTableListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Queries object.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {Provider, useResultTableListener} from 'tinybase/ui-react';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => {
useResultTableListener('petColors', () =>
console.log('Result table changed'),
);
return <span>App</span>;
};
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => select('color'),
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App queries={queries} />);
console.log(queries.getListenerStats().table);
// -> 1
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Result table changed'
root.unmount();
console.log(queries.getListenerStats().table);
// -> 0
Since
v2.0.0
useResultRowIds
The useResultRowIds hook returns the Ids of every Row in the ResultTable of the given query, and registers a listener so that any changes to those Ids will cause a re-render.
useResultRowIds(
queryId: string,
queriesOrQueriesId?: QueriesOrQueriesId,
): Ids| Type | Description | |
|---|---|---|
queryId | string | The |
queriesOrQueriesId? | QueriesOrQueriesId | The |
| returns | Ids | An array of the |
A Provider component is used to wrap part of an application in a context, and it can contain a default Queries object or a set of Queries objects named by Id. The useResultRowIds hook lets you indicate which Queries object to get data for: omit the final optional final parameter for the default context Queries object, provide an Id for a named context Queries object, or provide a Queries object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the result Row Ids will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Queries object outside the application, which is used in the useResultRowIds hook by reference. A change to the data in the query re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {useResultRowIds} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const App = () => (
<span>{JSON.stringify(useResultRowIds('dogColors', queries))}</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["fido","cujo"]</span>'
store.setCell('pets', 'cujo', 'species', 'wolf');
console.log(app.innerHTML);
// -> '<span>["fido"]</span>'
This example creates a Provider context into which a default Queries object is provided. A component within it then uses the useResultRowIds hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {Provider, useResultRowIds} from 'tinybase/ui-react';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useResultRowIds('dogColors'))}</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>["fido","cujo"]</span>'
This example creates a Provider context into which a Queries object is provided, named by Id. A component within it then uses the useResultRowIds hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {Provider, useResultRowIds} from 'tinybase/ui-react';
const App = ({queries}) => (
<Provider queriesById={{petQueries: queries}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useResultRowIds('dogColors', 'petQueries'))}</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>["fido","cujo"]</span>'
Since
v2.0.0
useResultRowIdsListener
The useResultRowIdsListener hook registers a listener function with a Queries object that will be called whenever the Row Ids in a ResultTable change.
useResultRowIdsListener(
queryId: IdOrNull,
listener: ResultRowIdsListener,
listenerDeps?: DependencyList,
queriesOrQueriesId?: QueriesOrQueriesId,
): void| Type | Description | |
|---|---|---|
queryId | IdOrNull | The |
listener | ResultRowIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
queriesOrQueriesId? | QueriesOrQueriesId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useResultRowIds hook).
You can either listen to a single ResultTable (by specifying a query Id as the method's first parameter) or changes to any ResultTable (by providing a null wildcard).
Unlike the addResultRowIdsListener method, which returns a listener Id and requires you to remove it manually, the useResultRowIdsListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Queries object will be deleted.
Example
This example uses the useResultRowIdsListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Queries object.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {Provider, useResultRowIdsListener} from 'tinybase/ui-react';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => {
useResultRowIdsListener('petColors', () =>
console.log('Result Row Ids changed'),
);
return <span>App</span>;
};
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => select('color'),
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App queries={queries} />);
console.log(queries.getListenerStats().rowIds);
// -> 1
store.setRow('pets', 'rex', {species: 'dog', color: 'tan'});
// -> 'Result Row Ids changed'
root.unmount();
console.log(queries.getListenerStats().rowIds);
// -> 0
Since
v2.0.0
useResultSortedRowIds
The useResultSortedRowIds hook returns the sorted (and optionally, paginated) Ids of every Row in the ResultTable of the given query, and registers a listener so that any changes to those Ids will cause a re-render.
useResultSortedRowIds(
queryId: string,
cellId?: string,
descending?: boolean,
offset?: number,
limit?: number,
queriesOrQueriesId?: QueriesOrQueriesId,
): Ids| Type | Description | |
|---|---|---|
queryId | string | The |
cellId? | string | The |
descending? | boolean | Whether the sorting should be in descending order. |
offset? | number | The number of |
limit? | number | The maximum number of |
queriesOrQueriesId? | QueriesOrQueriesId | The |
| returns | Ids | An array of the |
A Provider component is used to wrap part of an application in a context, and it can contain a default Queries object or a set of Queries objects named by Id. The useResultSortedRowIds hook lets you indicate which Queries object to get data for: omit the final optional final parameter for the default context Queries object, provide an Id for a named context Queries object, or provide a Queries object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the sorted result Row Ids will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Queries object outside the application, which is used in the useResultSortedRowIds hook by reference. A change to the data in the query re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {useResultSortedRowIds} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const App = () => (
<span>
{JSON.stringify(
useResultSortedRowIds(
'dogColors',
'color',
false,
0,
undefined,
queries,
),
)}
</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["cujo","fido"]</span>'
store.setCell('pets', 'cujo', 'species', 'wolf');
console.log(app.innerHTML);
// -> '<span>["fido"]</span>'
This example creates a Provider context into which a default Queries object is provided. A component within it then uses the useResultSortedRowIds hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {Provider, useResultSortedRowIds} from 'tinybase/ui-react';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{JSON.stringify(useResultSortedRowIds('dogColors', 'color'))}
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>["cujo","fido"]</span>'
This example creates a Provider context into which a Queries object is provided, named by Id. A component within it then uses the useResultSortedRowIds hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {Provider, useResultSortedRowIds} from 'tinybase/ui-react';
const App = ({queries}) => (
<Provider queriesById={{petQueries: queries}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{JSON.stringify(
useResultSortedRowIds(
'dogColors',
'color',
false,
0,
undefined,
'petQueries',
),
)}
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>["cujo","fido"]</span>'
Since
v2.0.0
useResultSortedRowIdsListener
The useResultSortedRowIdsListener hook registers a listener function with a Queries object that will be called whenever the sorted (and optionally, paginated) Row Ids in a ResultTable change.
useResultSortedRowIdsListener(
queryId: string,
cellId: undefined | string,
descending: boolean,
offset: number,
limit: undefined | number,
listener: ResultSortedRowIdsListener,
listenerDeps?: DependencyList,
queriesOrQueriesId?: QueriesOrQueriesId,
): void| Type | Description | |
|---|---|---|
queryId | string | The |
cellId | undefined | string | The |
descending | boolean | Whether the sorting should be in descending order. |
offset | number | The number of |
limit | undefined | number | The maximum number of |
listener | ResultSortedRowIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
queriesOrQueriesId? | QueriesOrQueriesId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useResultSortedRowIds hook).
Unlike the addResultSortedRowIdsListener method, which returns a listener Id and requires you to remove it manually, the useResultSortedRowIdsListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Queries object will be deleted.
Example
This example uses the useResultSortedRowIdsListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Queries object.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {Provider, useResultSortedRowIdsListener} from 'tinybase/ui-react';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => {
useResultSortedRowIdsListener(
'petColors',
'color',
false,
0,
undefined,
() => console.log('Sorted result Row Ids changed'),
);
return <span>App</span>;
};
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => select('color'),
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App queries={queries} />);
console.log(queries.getListenerStats().sortedRowIds);
// -> 1
store.setRow('pets', 'cujo', {color: 'tan'});
// -> 'Sorted result Row Ids changed'
root.unmount();
console.log(queries.getListenerStats().sortedRowIds);
// -> 0
Since
v2.0.0
useResultRow
The useResultRow hook returns an object containing the data of a single Row in the ResultTable of the given query, and registers a listener so that any changes to that Row will cause a re-render.
useResultRow(
queryId: string,
rowId: string,
queriesOrQueriesId?: QueriesOrQueriesId,
): Row| Type | Description | |
|---|---|---|
queryId | string | The |
rowId | string | The |
queriesOrQueriesId? | QueriesOrQueriesId | The |
| returns | Row | An object containing the entire data of the |
A Provider component is used to wrap part of an application in a context, and it can contain a default Queries object or a set of Queries objects named by Id. The useResultRow hook lets you indicate which Queries object to get data for: omit the final optional final parameter for the default context Queries object, provide an Id for a named context Queries object, or provide a Queries object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the result Row will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Queries object outside the application, which is used in the useResultRow hook by reference. A change to the data in the query re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {useResultRow} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const App = () => (
<span>{JSON.stringify(useResultRow('dogColors', 'fido', queries))}</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>{"color":"brown"}</span>'
store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<span>{"color":"walnut"}</span>'
This example creates a Provider context into which a default Queries object is provided. A component within it then uses the useResultRow hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {Provider, useResultRow} from 'tinybase/ui-react';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useResultRow('dogColors', 'fido'))}</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>{"color":"brown"}</span>'
This example creates a Provider context into which a Queries object is provided, named by Id. A component within it then uses the useResultRow hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {Provider, useResultRow} from 'tinybase/ui-react';
const App = ({queries}) => (
<Provider queriesById={{petQueries: queries}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{JSON.stringify(useResultRow('dogColors', 'fido', 'petQueries'))}
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>{"color":"brown"}</span>'
Since
v2.0.0
useResultRowCount
The useResultRowCount hook returns the count of the Row objects in the ResultTable of the given query, and registers a listener so that any changes to that result will cause a re-render.
useResultRowCount(
queryId: string,
queriesOrQueriesId?: QueriesOrQueriesId,
): number| Type | Description | |
|---|---|---|
queryId | string | The |
queriesOrQueriesId? | QueriesOrQueriesId | The |
| returns | number | The number of |
A Provider component is used to wrap part of an application in a context, and it can contain a default Queries object or a set of Queries objects named by Id. The useResultRowCount hook lets you indicate which Queries object to get data for: omit the final optional final parameter for the default context Queries object, provide an Id for a named context Queries object, or provide a Queries object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the count of ResultRow objects will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Queries object outside the application, which is used in the useResultRowCount hook by reference. A change to the data in the query re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {useResultRowCount} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const App = () => <span>{useResultRowCount('dogColors', queries)}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>2</span>'
store.setCell('pets', 'cujo', 'species', 'wolf');
console.log(app.innerHTML);
// -> '<span>1</span>'
This example creates a Provider context into which a default Queries object is provided. A component within it then uses the useResultRowCount hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {Provider, useResultRowCount} from 'tinybase/ui-react';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => <span>{useResultRowCount('dogColors')}</span>;
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>2</span>'
This example creates a Provider context into which a Queries object is provided, named by Id. A component within it then uses the useResultRowCount hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {Provider, useResultRowCount} from 'tinybase/ui-react';
const App = ({queries}) => (
<Provider queriesById={{petQueries: queries}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useResultRowCount('dogColors', 'petQueries')}</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>2</span>'
Since
v4.1.0
useResultRowCountListener
The useResultRowCountListener hook registers a listener function with a Queries object that will be called whenever the count of ResultRow objects in a ResultTable changes.
useResultRowCountListener(
queryId: IdOrNull,
listener: ResultRowCountListener,
listenerDeps?: DependencyList,
queriesOrQueriesId?: QueriesOrQueriesId,
): void| Type | Description | |
|---|---|---|
queryId | IdOrNull | The |
listener | ResultRowCountListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
queriesOrQueriesId? | QueriesOrQueriesId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useResultRowCount hook).
You can either listen to a single ResultTable (by specifying a query Id as the method's first parameter) or changes to any ResultTable (by providing a null wildcard).
Unlike the addResultRowCountListener method, which returns a listener Id and requires you to remove it manually, the useResultRowCountListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Queries object will be deleted.
Example
This example uses the useResultRowCountListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Queries object.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {Provider, useResultRowCountListener} from 'tinybase/ui-react';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => {
useResultRowCountListener('petColors', () =>
console.log('Result Row count changed'),
);
return <span>App</span>;
};
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => select('color'),
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App queries={queries} />);
console.log(queries.getListenerStats().rowCount);
// -> 1
store.setRow('pets', 'rex', {species: 'dog', color: 'tan'});
// -> 'Result Row count changed'
root.unmount();
console.log(queries.getListenerStats().rowCount);
// -> 0
Since
v4.1.0
useResultRowListener
The useResultRowListener hook registers a listener function with a Queries object that will be called whenever data in a result Row changes.
useResultRowListener(
queryId: IdOrNull,
rowId: IdOrNull,
listener: ResultRowListener,
listenerDeps?: DependencyList,
queriesOrQueriesId?: QueriesOrQueriesId,
): void| Type | Description | |
|---|---|---|
queryId | IdOrNull | The |
rowId | IdOrNull | The |
listener | ResultRowListener | The function that will be called whenever data in the matching result |
listenerDeps? | DependencyList | An optional array of dependencies for the |
queriesOrQueriesId? | QueriesOrQueriesId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useResultRow hook).
You can either listen to a single result Row (by specifying a query Id and Row Id as the method's first two parameters) or changes to any result Row (by providing null wildcards).
Both, either, or neither of the queryId and rowId parameters can be wildcarded with null. You can listen to a specific result Row in a specific query, any result Row in a specific query, a specific result Row in any query, or any result Row in any query.
Unlike the addResultRowListener method, which returns a listener Id and requires you to remove it manually, the useResultRowListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Queries object will be deleted.
Example
This example uses the useResultRowListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Queries object.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {Provider, useResultRowListener} from 'tinybase/ui-react';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => {
useResultRowListener('petColors', 'fido', () =>
console.log('Result row changed'),
);
return <span>App</span>;
};
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => select('color'),
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App queries={queries} />);
console.log(queries.getListenerStats().row);
// -> 1
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Result row changed'
root.unmount();
console.log(queries.getListenerStats().row);
// -> 0
Since
v2.0.0
useResultCellIds
The useResultCellIds hook returns the Ids of every Cell in a given Row in the ResultTable of the given query, and registers a listener so that any changes to those Ids will cause a re-render.
useResultCellIds(
queryId: string,
rowId: string,
queriesOrQueriesId?: QueriesOrQueriesId,
): Ids| Type | Description | |
|---|---|---|
queryId | string | The |
rowId | string | The |
queriesOrQueriesId? | QueriesOrQueriesId | The |
| returns | Ids | An array of the |
A Provider component is used to wrap part of an application in a context, and it can contain a default Queries object or a set of Queries objects named by Id. The useResultCellIds hook lets you indicate which Queries object to get data for: omit the final optional final parameter for the default context Queries object, provide an Id for a named context Queries object, or provide a Queries object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the result Cell Ids will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Queries object outside the application, which is used in the useResultCellIds hook by reference. A change to the data in the query re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {useResultCellIds} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('species');
select('color');
select('legs');
where('species', 'dog');
},
);
const App = () => (
<span>
{JSON.stringify(useResultCellIds('dogColors', 'fido', queries))}
</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["species","color"]</span>'
store.setCell('pets', 'fido', 'legs', 4);
console.log(app.innerHTML);
// -> '<span>["species","color","legs"]</span>'
This example creates a Provider context into which a default Queries object is provided. A component within it then uses the useResultCellIds hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {Provider, useResultCellIds} from 'tinybase/ui-react';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useResultCellIds('dogColors', 'fido'))}</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('species');
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>["species","color"]</span>'
This example creates a Provider context into which a Queries object is provided, named by Id. A component within it then uses the useResultCellIds hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {Provider, useResultCellIds} from 'tinybase/ui-react';
const App = ({queries}) => (
<Provider queriesById={{petQueries: queries}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{JSON.stringify(useResultCellIds('dogColors', 'fido', 'petQueries'))}
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('species');
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>["species","color"]</span>'
Since
v2.0.0
useResultCellIdsListener
The useResultCellIdsListener hook registers a listener function with a Queries object that will be called whenever the Cell Ids in a result Row change.
useResultCellIdsListener(
queryId: IdOrNull,
rowId: IdOrNull,
listener: ResultCellIdsListener,
listenerDeps?: DependencyList,
queriesOrQueriesId?: QueriesOrQueriesId,
): void| Type | Description | |
|---|---|---|
queryId | IdOrNull | The |
rowId | IdOrNull | The |
listener | ResultCellIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
queriesOrQueriesId? | QueriesOrQueriesId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useResultCellIds hook).
Both, either, or neither of the queryId and rowId parameters can be wildcarded with null. You can listen to a specific result Row in a specific query, any result Row in a specific query, a specific result Row in any query, or any result Row in any query.
Unlike the addResultCellIdsListener method, which returns a listener Id and requires you to remove it manually, the useResultCellIdsListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Queries object will be deleted.
Example
This example uses the useResultCellIdsListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Queries object.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {Provider, useResultCellIdsListener} from 'tinybase/ui-react';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => {
useResultCellIdsListener('petColors', 'fido', () =>
console.log('Result cell Ids changed'),
);
return <span>App</span>;
};
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => {
select('color');
select('legs');
},
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App queries={queries} />);
console.log(queries.getListenerStats().cellIds);
// -> 1
store.setCell('pets', 'fido', 'legs', 4);
// -> 'Result cell Ids changed'
root.unmount();
console.log(queries.getListenerStats().cellIds);
// -> 0
Since
v2.0.0
useResultCell
The useResultCell hook returns the value of a single Cell in a given Row in the ResultTable of the given query, and registers a listener so that any changes to that value will cause a re-render.
useResultCell(
queryId: string,
rowId: string,
cellId: string,
queriesOrQueriesId?: QueriesOrQueriesId,
): Cell | undefined| Type | Description | |
|---|---|---|
queryId | string | The |
rowId | string | The |
cellId | string | |
queriesOrQueriesId? | QueriesOrQueriesId | The |
| returns | Cell | undefined | The value of the |
A Provider component is used to wrap part of an application in a context, and it can contain a default Queries object or a set of Queries objects named by Id. The useResultCell hook lets you indicate which Queries object to get data for: omit the final optional final parameter for the default context Queries object, provide an Id for a named context Queries object, or provide a Queries object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the result Cell will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Queries object outside the application, which is used in the useResultCell hook by reference. A change to the data in the query re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {useResultCell} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('species');
select('color');
select('legs');
where('species', 'dog');
},
);
const App = () => (
<span>{useResultCell('dogColors', 'fido', 'color', queries)}</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>brown</span>'
store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<span>walnut</span>'
This example creates a Provider context into which a default Queries object is provided. A component within it then uses the useResultCell hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {Provider, useResultCell} from 'tinybase/ui-react';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useResultCell('dogColors', 'fido', 'color')}</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('species');
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>brown</span>'
This example creates a Provider context into which a Queries object is provided, named by Id. A component within it then uses the useResultCell hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {Provider, useResultCell} from 'tinybase/ui-react';
const App = ({queries}) => (
<Provider queriesById={{petQueries: queries}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useResultCell('dogColors', 'fido', 'color', 'petQueries')}</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('species');
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>brown</span>'
Since
v2.0.0
useResultCellListener
The useResultCellListener hook registers a listener function with a Queries object that will be called whenever data in a Cell changes.
useResultCellListener(
queryId: IdOrNull,
rowId: IdOrNull,
cellId: IdOrNull,
listener: ResultCellListener,
listenerDeps?: DependencyList,
queriesOrQueriesId?: QueriesOrQueriesId,
): void| Type | Description | |
|---|---|---|
queryId | IdOrNull | The |
rowId | IdOrNull | The |
cellId | IdOrNull | The |
listener | ResultCellListener | The function that will be called whenever data in the matching result |
listenerDeps? | DependencyList | An optional array of dependencies for the |
queriesOrQueriesId? | QueriesOrQueriesId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useResultCell hook).
You can either listen to a single Cell (by specifying the Table Id, Row Id, and Cell Id as the method's first three parameters) or changes to any Cell (by providing null wildcards).
All, some, or none of the queryId, rowId, and cellId parameters can be wildcarded with null. You can listen to a specific Cell in a specific result Row in a specific query, any Cell in any result Row in any query, for example - or every other combination of wildcards.
Unlike the addResultCellListener method, which returns a listener Id and requires you to remove it manually, the useResultCellListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Queries object will be deleted.
Example
This example uses the useResultCellListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Queries object.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {Provider, useResultCellListener} from 'tinybase/ui-react';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => {
useResultCellListener('petColors', 'fido', 'color', () =>
console.log('Result cell changed'),
);
return <span>App</span>;
};
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => select('color'),
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App queries={queries} />);
console.log(queries.getListenerStats().cell);
// -> 1
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Result cell changed'
root.unmount();
console.log(queries.getListenerStats().cell);
// -> 0
Since
v2.0.0
useCreateQueries
The useCreateQueries hook is used to create a Queries object within a React application with convenient memoization.
useCreateQueries(
store: undefined | Store,
create: (store: Store) => Queries,
createDeps?: DependencyList,
): Queries | undefined| Type | Description | |
|---|---|---|
store | undefined | Store | A reference to the |
create | (store: Store) => Queries | An optional callback for performing post-creation steps on the |
createDeps? | DependencyList | An optional array of dependencies for the |
| returns | Queries | undefined | A reference to the |
It is possible to create a Queries object outside of the React app with the regular createQueries function and pass it in, but you may prefer to create it within the app, perhaps inside the top-level component. To prevent a new Queries object being created every time the app renders or re-renders, since v5.0 this hook performs the creation in an effect. As a result it will return undefined on the brief first render (or if the Store is not yet defined), which you should defend against.
If your create function contains other dependencies, the changing of which should also cause the Queries object to be recreated, you can provide them in an array in the optional second parameter, just as you would for any React hook with dependencies.
This hook ensures the Queries object is destroyed whenever a new one is created or the component is unmounted.
Examples
This example creates a Queries object at the top level of a React application. Even though the App component is rendered twice, the Queries object creation only occurs once by default.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {useCreateQueries, useCreateStore} from 'tinybase/ui-react';
const App = () => {
const store = useCreateStore(() =>
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
);
const queries = useCreateQueries(store, (store) => {
console.log('Queries created');
return createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
});
return (
<span>{queries?.getResultCell('dogColors', 'fido', 'color')}</span>
);
};
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
// -> 'Queries created'
root.render(<App />);
// No second Queries creation
console.log(app.innerHTML);
// -> '<span>brown</span>'
This example creates a Queries object at the top level of a React application. The App component is rendered twice, each with a different top-level prop. The useCreateQueries hook takes the resultCell prop as a dependency, and so the Queries object is created again on the second render.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {useCreateQueries, useCreateStore} from 'tinybase/ui-react';
const App = () => {
const store = useCreateStore(() =>
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
);
const queries = useCreateQueries(store, (store) => {
console.log('Queries created');
return createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
});
return (
<span>{queries?.getResultCell('dogColors', 'fido', 'color')}</span>
);
};
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
// -> 'Queries created'
root.render(<App />);
// No second Queries creation
console.log(app.innerHTML);
// -> '<span>brown</span>'
Since
v2.0.0
useProvideQueries
The useProvideQueries hook is used to add a Queries object by Id to a Provider component, but imperatively from a component within it.
useProvideQueries(
queriesId: string,
queries: Queries,
): void| Type | Description | |
|---|---|---|
queriesId | string | The |
queries | Queries | The |
| returns | void | This has no return value. |
Normally you will register a Queries object by Id in a context by using the queriesById prop of the top-level Provider component. This hook, however, lets you dynamically add a new Queries object to the context, from within a component. This is useful for applications where the set of Queries objects is not known at the time of the first render of the root Provider.
A Queries object added to the Provider context in this way will be available to other components within the context (using the useQueries hook and so on). If you use the same Id as an existing Queries object registration, the new one will take priority over one provided by the queriesById prop.
Note that other components that consume a Queries object registered like this should defend against it being undefined at first. On the first render, the other component will likely not yet have completed the registration. In the example below, we use the null-safe useQueries('petQueries')? to do this.
Example
This example creates a Provider context. A child component registers a Queries object into it which is then consumable by a peer child component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {
Provider,
useCreateQueries,
useCreateStore,
useProvideQueries,
useQueries,
} from 'tinybase/ui-react';
const App = () => (
<Provider>
<RegisterQueries />
<ConsumeQueries />
</Provider>
);
const RegisterQueries = () => {
const store = useCreateStore(() =>
createStore().setRow('pets', 'fido', {color: 'brown', legs: 4}),
);
const queries = useCreateQueries(store, (store) =>
createQueries(store).setQueryDefinition(
'brownLegs',
'pets',
({select, where}) => {
select('legs');
where('color', 'brown');
},
),
);
useProvideQueries('petQueries', queries);
return null;
};
const ConsumeQueries = () => (
<span>
{JSON.stringify(useQueries('petQueries')?.getResultTable('brownLegs'))}
</span>
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
console.log(app.innerHTML);
// -> '<span>{\"fido\":{\"legs\":4}}</span>'
Since
v5.3.0
useQueries
The useQueries hook is used to get a reference to a Queries object from within a Provider component context.
useQueries(id?: string): Queries | undefined| Type | Description | |
|---|---|---|
id? | string | An optional |
| returns | Queries | undefined | A reference to the |
A Provider component is used to wrap part of an application in a context. It can contain a default Queries object (or a set of Queries objects named by Id) that can be easily accessed without having to be passed down as props through every component.
The useQueries hook lets you either get a reference to the default Queries object (when called without a parameter), or one of the Queries objects that are named by Id (when called with an Id parameter).
Examples
This example creates a Provider context into which a default Queries object is provided. A component within it then uses the useQueries hook to get a reference to the Queries object again, without the need to have it passed as a prop.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {Provider, useQueries} from 'tinybase/ui-react';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => <span>{useQueries().getListenerStats().table}</span>;
const queries = createQueries(createStore());
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
This example creates a Provider context into which a Queries object is provided, named by Id. A component within it then uses the useQueries hook with that Id to get a reference to the Queries object again, without the need to have it passed as a prop.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {Provider, useQueries} from 'tinybase/ui-react';
const App = ({queries}) => (
<Provider queriesById={{petQueries: queries}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useQueries('petQueries').getListenerStats().table}</span>
);
const queries = createQueries(createStore());
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
Since
v2.0.0
useQueriesIds
The useQueriesIds hook is used to retrieve the Ids of all the named Queries objects present in the current Provider component context.
useQueriesIds(): IdsExample
This example adds two named Queries objects to a Provider context and an inner component accesses their Ids.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {
Provider,
useCreateQueries,
useCreateStore,
useQueriesIds,
} from 'tinybase/ui-react';
const App = () => {
const store1 = useCreateStore(createStore);
const queries1 = useCreateQueries(store1, createQueries);
const store2 = useCreateStore(createStore);
const queries2 = useCreateQueries(store2, createQueries);
return (
<Provider queriesById={{queries1, queries2}}>
<Pane />
</Provider>
);
};
const Pane = () => <span>{JSON.stringify(useQueriesIds())}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["queries1","queries2"]</span>'
Since
v4.1.0
useQueriesOrQueriesById
The useQueriesOrQueriesById hook is used to get a reference to a Queries object from within a Provider component context, or have it passed directly to this hook.
useQueriesOrQueriesById(queriesOrQueriesId?: QueriesOrQueriesId): Queries | undefined| Type | Description | |
|---|---|---|
queriesOrQueriesId? | QueriesOrQueriesId | Either an |
| returns | Queries | undefined | A reference to the |
This is mostly of use when you are developing a component that needs a Queries object and which might have been passed in explicitly to the component or is to be picked up from the context by Id (a common pattern for Queries-based components).
This is unlikely to be used often. For most situations, you will want to use the useQueries hook.
Example
This example creates a Provider context into which a default Queries object is provided. A component within it then uses the useQueriesOrQueriesById hook to get a reference to the Queries object again, without the need to have it passed as a prop. Note however, that unlike the useQueries hook example, this component would also work if you were to pass the Queries object directly into it, making it more portable.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {Provider, useQueriesOrQueriesById} from 'tinybase/ui-react';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = ({queries}) => (
<span>
{JSON.stringify(useQueriesOrQueriesById(queries).getQueryIds())}
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>["dogColors"]</span>'
Since
v4.1.0
useQueryIds
The useQueryIds hook gets an array of the Query Ids registered with a Queries object, and registers a listener so that any changes to that result will cause a re-render.
useQueryIds(queriesOrQueriesId?: QueriesOrQueriesId): Ids| Type | Description | |
|---|---|---|
queriesOrQueriesId? | QueriesOrQueriesId | The |
| returns | Ids |
A Provider component is used to wrap part of an application in a context, and it can contain a default Queries object or a set of Queries objects named by Id. The useQueryIds hook lets you indicate which Queries object to get data for: omit the optional final parameter for the default context Queries object, provide an Id for a named context Queries object, or provide a Queries object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Query Ids in the Queries object will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Example
This example creates an Queries object outside the application, which is used in the useQueryIds hook by reference. A newly-registered Relationship re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {useQueryIds} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const queries = createQueries(store);
const App = () => <span>{JSON.stringify(useQueryIds(queries))}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>[]</span>'
const addQueryDefinition = () =>
queries.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
addQueryDefinition();
console.log(app.innerHTML);
// -> '<span>["dogColors"]</span>'
Since
v4.1.0
Relationships hooks
useLinkedRowIds
The useLinkedRowIds hook gets the linked Row Ids for a given Row in a linked list Relationship, and registers a listener so that any changes to that result will cause a re-render.
useLinkedRowIds(
relationshipId: string,
firstRowId: string,
relationshipsOrRelationshipsId?: RelationshipsOrRelationshipsId,
): Ids| Type | Description | |
|---|---|---|
relationshipId | string | The |
firstRowId | string | The |
relationshipsOrRelationshipsId? | RelationshipsOrRelationshipsId | The |
| returns | Ids | The linked |
A Provider component is used to wrap part of an application in a context, and it can contain a default Relationships object or a set of Relationships objects named by Id. The useLinkedRowIds hook lets you indicate which Relationships object to get data for: omit the optional final parameter for the default context Relationships object, provide an Id for a named context Relationships object, or provide a Relationships object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the linked Row Ids will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Relationships object outside the application, which is used in the useLinkedRowIds hook by reference. A change to the linked Row Ids re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createRelationships, createStore} from 'tinybase';
import {useLinkedRowIds} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {
fido: {species: 'dog', next: 'felix'},
felix: {species: 'cat', next: 'cujo'},
cujo: {species: 'dog'},
});
const relationships = createRelationships(store).setRelationshipDefinition(
'petSequence',
'pets',
'pets',
'next',
);
const App = () => (
<span>
{JSON.stringify(useLinkedRowIds('petSequence', 'fido', relationships))}
</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["fido","felix","cujo"]</span>'
store.setRow('pets', 'toto', {species: 'dog'});
store.setCell('pets', 'cujo', 'next', 'toto');
console.log(app.innerHTML);
// -> '<span>["fido","felix","cujo","toto"]</span>'
This example creates a Provider context into which a default Relationships object is provided. A component within it then uses the useLinkedRowIds hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createRelationships, createStore} from 'tinybase';
import {Provider, useLinkedRowIds} from 'tinybase/ui-react';
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useLinkedRowIds('petSequence', 'fido'))}</span>
);
const relationships = createRelationships(
createStore().setTable('pets', {
fido: {species: 'dog', next: 'felix'},
felix: {species: 'cat', next: 'cujo'},
cujo: {species: 'dog'},
}),
).setRelationshipDefinition('petSequence', 'pets', 'pets', 'next');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<span>["fido","felix","cujo"]</span>'
This example creates a Provider context into which a default Relationships object is provided. A component within it then uses the useLinkedRowIds hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createRelationships, createStore} from 'tinybase';
import {Provider, useLinkedRowIds} from 'tinybase/ui-react';
const App = ({relationships}) => (
<Provider relationshipsById={{petRelationships: relationships}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{JSON.stringify(
useLinkedRowIds('petSequence', 'fido', 'petRelationships'),
)}
</span>
);
const relationships = createRelationships(
createStore().setTable('pets', {
fido: {species: 'dog', next: 'felix'},
felix: {species: 'cat', next: 'cujo'},
cujo: {species: 'dog'},
}),
).setRelationshipDefinition('petSequence', 'pets', 'pets', 'next');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<span>["fido","felix","cujo"]</span>'
Since
v1.0.0
useLinkedRowIdsListener
The useLinkedRowIdsListener hook registers a listener function with the Relationships object that will be called whenever the linked Row Ids in a Relationship change.
useLinkedRowIdsListener(
relationshipId: string,
firstRowId: string,
listener: LinkedRowIdsListener,
listenerDeps?: DependencyList,
relationshipsOrRelationshipsId?: RelationshipsOrRelationshipsId,
): void| Type | Description | |
|---|---|---|
relationshipId | string | The |
firstRowId | string | |
listener | LinkedRowIdsListener | The function that will be called whenever the linked |
listenerDeps? | DependencyList | An optional array of dependencies for the |
relationshipsOrRelationshipsId? | RelationshipsOrRelationshipsId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useLinkedRowsId hook).
Unlike other listener registration methods, you cannot provide null wildcards for the first two parameters of the useLinkedRowIdsListener method. This prevents the prohibitive expense of tracking all the possible linked lists (and partial linked lists within them) in a Store.
Unlike the addLinkedRowsIdListener method, which returns a listener Id and requires you to remove it manually, the useLinkedRowIdsListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Indexes object will be deleted.
Example
This example uses the useLinkedRowIdsListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Relationships object.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createRelationships, createStore} from 'tinybase';
import {Provider, useLinkedRowIdsListener} from 'tinybase/ui-react';
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = () => {
useLinkedRowIdsListener('petSequence', 'fido', () =>
console.log('Linked Row Ids changed'),
);
return <span>App</span>;
};
const store = createStore().setTable('pets', {
fido: {species: 'dog', next: 'felix'},
felix: {species: 'cat', next: 'cujo'},
cujo: {species: 'dog'},
});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSequence',
'pets',
'pets',
'next',
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(relationships.getListenerStats().linkedRowIds);
// -> 1
store.setRow('pets', 'toto', {species: 'dog'});
store.setCell('pets', 'cujo', 'next', 'toto');
// -> 'Linked Row Ids changed'
root.unmount();
console.log(relationships.getListenerStats().linkedRowIds);
// -> 0
Since
v1.0.0
useLocalRowIds
The useLocalRowIds hook gets the local Row Ids for a given remote Row in a Relationship, and registers a listener so that any changes to that result will cause a re-render.
useLocalRowIds(
relationshipId: string,
remoteRowId: string,
relationshipsOrRelationshipsId?: RelationshipsOrRelationshipsId,
): Ids| Type | Description | |
|---|---|---|
relationshipId | string | The |
remoteRowId | string | The |
relationshipsOrRelationshipsId? | RelationshipsOrRelationshipsId | The |
| returns | Ids | The local |
A Provider component is used to wrap part of an application in a context, and it can contain a default Relationships object or a set of Relationships objects named by Id. The useLocalRowIds hook lets you indicate which Relationships object to get data for: omit the optional final parameter for the default context Relationships object, provide an Id for a named context Relationships object, or provide a Relationships object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the local Row Id will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Relationships object outside the application, which is used in the useLocalRowIds hook by reference. A change to the local Row Ids re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createRelationships, createStore} from 'tinybase';
import {useLocalRowIds} from 'tinybase/ui-react';
const store = createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}});
const relationships = createRelationships(store).setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
const App = () => (
<span>
{JSON.stringify(useLocalRowIds('petSpecies', 'dog', relationships))}
</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["fido","cujo"]</span>'
store.setRow('pets', 'toto', {species: 'dog'});
console.log(app.innerHTML);
// -> '<span>["fido","cujo","toto"]</span>'
This example creates a Provider context into which a default Relationships object is provided. A component within it then uses the useLocalRowIds hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createRelationships, createStore} from 'tinybase';
import {Provider, useLocalRowIds} from 'tinybase/ui-react';
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useLocalRowIds('petSpecies', 'dog'))}</span>
);
const relationships = createRelationships(
createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<span>["fido","cujo"]</span>'
This example creates a Provider context into which a default Relationships object is provided. A component within it then uses the useLocalRowIds hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createRelationships, createStore} from 'tinybase';
import {Provider, useLocalRowIds} from 'tinybase/ui-react';
const App = ({relationships}) => (
<Provider relationshipsById={{petRelationships: relationships}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{JSON.stringify(
useLocalRowIds('petSpecies', 'dog', 'petRelationships'),
)}
</span>
);
const relationships = createRelationships(
createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<span>["fido","cujo"]</span>'
Since
v1.0.0
useLocalRowIdsListener
The useLocalRowIdsListener hook registers a listener function with the Relationships object that will be called whenever the local Row Ids in a Relationship change.
useLocalRowIdsListener(
relationshipId: IdOrNull,
remoteRowId: IdOrNull,
listener: LocalRowIdsListener,
listenerDeps?: DependencyList,
relationshipsOrRelationshipsId?: RelationshipsOrRelationshipsId,
): void| Type | Description | |
|---|---|---|
relationshipId | IdOrNull | The |
remoteRowId | IdOrNull | The |
listener | LocalRowIdsListener | The function that will be called whenever the local |
listenerDeps? | DependencyList | An optional array of dependencies for the |
relationshipsOrRelationshipsId? | RelationshipsOrRelationshipsId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useLocalRowsId hook).
You can either listen to a single local Row (by specifying the Relationship Id and local Row Id as the method's first two parameters), or changes to any local Row (by providing a null wildcards).
Both, either, or neither of the relationshipId and remoteRowId parameters can be wildcarded with null. You can listen to a specific remote Row in a specific Relationship, any remote Row in a specific Relationship, a specific remote Row in any Relationship, or any remote Row in any Relationship.
Unlike the addLocalRowsIdListener method, which returns a listener Id and requires you to remove it manually, the useLocalRowIdsListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Indexes object will be deleted.
Example
This example uses the useLocalRowIdsListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Relationships object.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createRelationships, createStore} from 'tinybase';
import {Provider, useLocalRowIdsListener} from 'tinybase/ui-react';
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = () => {
useLocalRowIdsListener('petSpecies', 'dog', () =>
console.log('Local Row Ids changed'),
);
return <span>App</span>;
};
const store = createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(relationships.getListenerStats().localRowIds);
// -> 1
store.setRow('pets', 'toto', {species: 'dog'});
// -> 'Local Row Ids changed'
root.unmount();
console.log(relationships.getListenerStats().localRowIds);
// -> 0
Since
v1.0.0
useRemoteRowId
The useRemoteRowId hook gets the remote Row Id for a given local Row in a Relationship, and registers a listener so that any changes to that result will cause a re-render.
useRemoteRowId(
relationshipId: string,
localRowId: string,
relationshipsOrRelationshipsId?: RelationshipsOrRelationshipsId,
): Id | undefined| Type | Description | |
|---|---|---|
relationshipId | string | The |
localRowId | string | The |
relationshipsOrRelationshipsId? | RelationshipsOrRelationshipsId | The |
| returns | Id | undefined | The remote |
A Provider component is used to wrap part of an application in a context, and it can contain a default Relationships object or a set of Relationships objects named by Id. The useRemoteRowId hook lets you indicate which Relationships object to get data for: omit the optional final parameter for the default context Relationships object, provide an Id for a named context Relationships object, or provide a Relationships object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the remote Row Id will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Relationships object outside the application, which is used in the useRemoteRowId hook by reference. A change to the remote Row Id re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createRelationships, createStore} from 'tinybase';
import {useRemoteRowId} from 'tinybase/ui-react';
const store = createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}});
const relationships = createRelationships(store).setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
const App = () => (
<span>{useRemoteRowId('petSpecies', 'cujo', relationships)}</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>dog</span>'
store.setCell('pets', 'cujo', 'species', 'wolf');
console.log(app.innerHTML);
// -> '<span>wolf</span>'
This example creates a Provider context into which a default Relationships object is provided. A component within it then uses the useRemoteRowId hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createRelationships, createStore} from 'tinybase';
import {Provider, useRemoteRowId} from 'tinybase/ui-react';
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = () => <span>{useRemoteRowId('petSpecies', 'cujo')}</span>;
const relationships = createRelationships(
createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<span>dog</span>'
This example creates a Provider context into which a default Relationships object is provided. A component within it then uses the useRemoteRowId hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createRelationships, createStore} from 'tinybase';
import {Provider, useRemoteRowId} from 'tinybase/ui-react';
const App = ({relationships}) => (
<Provider relationshipsById={{petRelationships: relationships}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useRemoteRowId('petSpecies', 'cujo', 'petRelationships')}</span>
);
const relationships = createRelationships(
createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<span>dog</span>'
Since
v1.0.0
useRemoteRowIdListener
The useRemoteRowIdListener hook registers a listener function with the Relationships object that will be called whenever a remote Row Id in a Relationship changes.
useRemoteRowIdListener(
relationshipId: IdOrNull,
localRowId: IdOrNull,
listener: RemoteRowIdListener,
listenerDeps?: DependencyList,
relationshipsOrRelationshipsId?: RelationshipsOrRelationshipsId,
): void| Type | Description | |
|---|---|---|
relationshipId | IdOrNull | The |
localRowId | IdOrNull | The |
listener | RemoteRowIdListener | The function that will be called whenever the remote |
listenerDeps? | DependencyList | An optional array of dependencies for the |
relationshipsOrRelationshipsId? | RelationshipsOrRelationshipsId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useRemoteRowId hook).
You can either listen to a single local Row (by specifying the Relationship Id and local Row Id as the method's first two parameters), or changes to any local Row (by providing a null wildcards).
Both, either, or neither of the relationshipId and localRowId parameters can be wildcarded with null. You can listen to a specific local Row in a specific Relationship, any local Row in a specific Relationship, a specific local Row in any Relationship, or any local Row in any Relationship.
Unlike the addRemoteRowIdListener method, which returns a listener Id and requires you to remove it manually, the useRemoteRowIdListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Indexes object will be deleted.
Example
This example uses the useRemoteRowIdListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Relationships object.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createRelationships, createStore} from 'tinybase';
import {Provider, useRemoteRowIdListener} from 'tinybase/ui-react';
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = () => {
useRemoteRowIdListener('petSpecies', 'cujo', () =>
console.log('Remote Row Id changed'),
);
return <span>App</span>;
};
const store = createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(relationships.getListenerStats().remoteRowId);
// -> 1
store.setCell('pets', 'cujo', 'species', 'wolf');
// -> 'Remote Row Id changed'
root.unmount();
console.log(relationships.getListenerStats().remoteRowId);
// -> 0
Since
v1.0.0
useCreateRelationships
The useCreateRelationships hook is used to create a Relationships object within a React application with convenient memoization.
useCreateRelationships(
store: undefined | Store,
create: (store: Store) => Relationships,
createDeps?: DependencyList,
): Relationships | undefined| Type | Description | |
|---|---|---|
store | undefined | Store | A reference to the |
create | (store: Store) => Relationships | An optional callback for performing post-creation steps on the |
createDeps? | DependencyList | An optional array of dependencies for the |
| returns | Relationships | undefined | A reference to the |
It is possible to create a Relationships object outside of the React app with the regular createRelationships function and pass it in, but you may prefer to create it within the app, perhaps inside the top-level component. To prevent a new Relationships object being created every time the app renders or re-renders, since v5.0 this hook performs the creation in an effect. As a result it will return undefined on the brief first render (or if the Store is not yet defined), which you should defend against.
If your create function contains other dependencies, the changing of which should also cause the Relationships object to be recreated, you can provide them in an array in the optional second parameter, just as you would for any React hook with dependencies.
This hook ensures the Relationships object is destroyed whenever a new one is created or the component is unmounted.
Examples
This example creates a Relationships object at the top level of a React application. Even though the App component is rendered twice, the Relationships object creation only occurs once by default.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createRelationships, createStore} from 'tinybase';
import {useCreateRelationships, useCreateStore} from 'tinybase/ui-react';
const App = () => {
const store = useCreateStore(() =>
createStore()
.setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
})
.setTable('species', {dog: {price: 5}, cat: {price: 4}}),
);
const relationships = useCreateRelationships(store, (store) => {
console.log('Relationships created');
return createRelationships(store).setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
});
return (
<span>{relationships?.getRemoteRowId('petSpecies', 'fido')}</span>
);
};
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
// -> 'Relationships created'
root.render(<App />);
// No second Relationships creation
console.log(app.innerHTML);
// -> '<span>dog</span>'
This example creates a Relationships object at the top level of a React application. The App component is rendered twice, each with a different top-level prop. The useCreateRelationships hook takes the remoteTableAndCellToLink prop as a dependency, and so the Relationships object is created again on the second render.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createRelationships, createStore} from 'tinybase';
import {useCreateRelationships, useCreateStore} from 'tinybase/ui-react';
const App = ({remoteTableAndCellToLink}) => {
const store = useCreateStore(() =>
createStore()
.setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'brown'},
})
.setTable('species', {dog: {price: 5}, cat: {price: 4}})
.setTable('color', {brown: {discount: 0.1}, black: {discount: 0}}),
);
const relationships = useCreateRelationships(
store,
(store) => {
console.log(`Relationship created to ${remoteTableAndCellToLink}`);
return createRelationships(store).setRelationshipDefinition(
'cellLinked',
'pets',
remoteTableAndCellToLink,
remoteTableAndCellToLink,
);
},
[remoteTableAndCellToLink],
);
return (
<span>{relationships?.getRemoteRowId('cellLinked', 'fido')}</span>
);
};
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App remoteTableAndCellToLink="species" />);
// -> 'Relationship created to species'
console.log(app.innerHTML);
// -> '<span>dog</span>'
root.render(<App remoteTableAndCellToLink="color" />);
// -> 'Relationship created to color'
console.log(app.innerHTML);
// -> '<span>brown</span>'
Since
v1.0.0
useProvideRelationships
The useProvideRelationships hook is used to add a Relationships object by Id to a Provider component, but imperatively from a component within it.
useProvideRelationships(
relationshipsId: string,
relationships: Relationships,
): void| Type | Description | |
|---|---|---|
relationshipsId | string | The |
relationships | Relationships | The |
| returns | void | This has no return value. |
Normally you will register a Relationships object by Id in a context by using the relationshipsById prop of the top-level Provider component. This hook, however, lets you dynamically add a new Relationships object to the context, from within a component. This is useful for applications where the set of Relationships objects is not known at the time of the first render of the root Provider.
A Relationships object added to the Provider context in this way will be available to other components within the context (using the useRelationships hook and so on). If you use the same Id as an existing Relationships object registration, the new one will take priority over one provided by the relationshipsById prop.
Note that other components that consume a Relationships object registered like this should defend against it being undefined at first. On the first render, the other component will likely not yet have completed the registration. In the example below, we use the null-safe useRelationships('petRelationships')? to do this.
Example
This example creates a Provider context. A child component registers a Relationships object into it which is then consumable by a peer child component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createRelationships, createStore} from 'tinybase';
import {
Provider,
useCreateRelationships,
useCreateStore,
useProvideRelationships,
useRelationships,
} from 'tinybase/ui-react';
const App = () => (
<Provider>
<RegisterRelationships />
<ConsumeRelationships />
</Provider>
);
const RegisterRelationships = () => {
const store = useCreateStore(() =>
createStore()
.setTable('pets', {fido: {species: 'dog'}})
.setTable('species', {dog: {price: 5}}),
);
const relationships = useCreateRelationships(store, (store) =>
createRelationships(store).setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
),
);
useProvideRelationships('petRelationships', relationships);
return null;
};
const ConsumeRelationships = () => (
<span>
{useRelationships('petRelationships')?.getRemoteRowId(
'petSpecies',
'fido',
)}
</span>
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
console.log(app.innerHTML);
// -> '<span>dog</span>'
Since
v5.3.0
useRelationshipIds
The useRelationshipIds hook gets an array of the Relationship Ids registered with a Relationships object, and registers a listener so that any changes to that result will cause a re-render.
useRelationshipIds(relationshipsOrRelationshipsId?: RelationshipsOrRelationshipsId): Ids| Type | Description | |
|---|---|---|
relationshipsOrRelationshipsId? | RelationshipsOrRelationshipsId | The |
| returns | Ids | The |
A Provider component is used to wrap part of an application in a context, and it can contain a default Relationships object or a set of Relationships objects named by Id. The useRelationshipIds hook lets you indicate which Relationships object to get data for: omit the optional final parameter for the default context Relationships object, provide an Id for a named context Relationships object, or provide a Relationships object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Relationship Ids in the Relationships object will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Example
This example creates an Relationships object outside the application, which is used in the useRelationshipIds hook by reference. A newly-registered Relationship re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createRelationships, createStore} from 'tinybase';
import {useRelationshipIds} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const relationships = createRelationships(store);
const App = () => (
<span>{JSON.stringify(useRelationshipIds(relationships))}</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>[]</span>'
const addRelationshipDefinition = () =>
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
addRelationshipDefinition();
console.log(app.innerHTML);
// -> '<span>["petSpecies"]</span>'
Since
v4.1.0
useRelationships
The useRelationships hook is used to get a reference to a Relationships object from within a Provider component context.
useRelationships(id?: string): Relationships | undefined| Type | Description | |
|---|---|---|
id? | string | An optional |
| returns | Relationships | undefined | A reference to the |
A Provider component is used to wrap part of an application in a context. It can contain a default Relationships object (or a set of Relationships objects named by Id) that can be easily accessed without having to be passed down as props through every component.
The useRelationships hook lets you either get a reference to the default Relationships object (when called without a parameter), or one of the Relationships objects that are named by Id (when called with an Id parameter).
Examples
This example creates a Provider context into which a default Relationships object is provided. A component within it then uses the useRelationships hook to get a reference to the Relationships object again, without the need to have it passed as a prop.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createRelationships, createStore} from 'tinybase';
import {Provider, useRelationships} from 'tinybase/ui-react';
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useRelationships().getListenerStats().remoteRowId}</span>
);
const relationships = createRelationships(createStore());
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
This example creates a Provider context into which a Relationships object is provided, named by Id. A component within it then uses the useRelationships hook with that Id to get a reference to the Relationships object again, without the need to have it passed as a prop.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createRelationships, createStore} from 'tinybase';
import {Provider, useRelationships} from 'tinybase/ui-react';
const App = ({relationships}) => (
<Provider relationshipsById={{petStore: relationships}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{useRelationships('petStore').getListenerStats().remoteRowId}
</span>
);
const relationships = createRelationships(createStore());
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
Since
v1.0.0
useRelationshipsIds
The useRelationshipsIds hook is used to retrieve the Ids of all the named Relationships objects present in the current Provider component context.
useRelationshipsIds(): IdsExample
This example adds two named Relationships objects to a Provider context and an inner component accesses their Ids.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createRelationships, createStore} from 'tinybase';
import {
Provider,
useCreateRelationships,
useCreateStore,
useRelationshipsIds,
} from 'tinybase/ui-react';
const App = () => {
const store1 = useCreateStore(createStore);
const relationships1 = useCreateRelationships(
store1,
createRelationships,
);
const store2 = useCreateStore(createStore);
const relationships2 = useCreateRelationships(
store2,
createRelationships,
);
return (
<Provider relationshipsById={{relationships1, relationships2}}>
<Pane />
</Provider>
);
};
const Pane = () => <span>{JSON.stringify(useRelationshipsIds())}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["relationships1","relationships2"]</span>'
Since
v4.1.0
useRelationshipsOrRelationshipsById
The useRelationshipsOrRelationshipsById hook is used to get a reference to a Relationships object from within a Provider component context, or have it passed directly to this hook.
useRelationshipsOrRelationshipsById(relationshipsOrRelationshipsId?: RelationshipsOrRelationshipsId): Relationships | undefined| Type | Description | |
|---|---|---|
relationshipsOrRelationshipsId? | RelationshipsOrRelationshipsId | Either an |
| returns | Relationships | undefined | A reference to the |
This is mostly of use when you are developing a component that needs a Relationships object and which might have been passed in explicitly to the component or is to be picked up from the context by Id (a common pattern for Relationships-based components).
This is unlikely to be used often. For most situations, you will want to use the useRelationships hook.
Example
This example creates a Provider context into which a default Relationships object is provided. A component within it then uses the useRelationshipsOrRelationshipsById hook to get a reference to the Relationships object again, without the need to have it passed as a prop. Note however, that unlike the useRelationships hook example, this component would also work if you were to pass the Relationships object directly into it, making it more portable.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createRelationships, createStore} from 'tinybase';
import {
Provider,
useRelationshipsOrRelationshipsById,
} from 'tinybase/ui-react';
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = ({relationships}) => (
<span>
{JSON.stringify(
useRelationshipsOrRelationshipsById(
relationships,
).getRelationshipIds(),
)}
</span>
);
const relationships = createRelationships(
createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<span>["petSpecies"]</span>'
Since
v4.1.0
Store hooks
useCreateMergeableStore
The useCreateMergeableStore hook.
useCreateMergeableStore(
create: () => MergeableStore,
createDeps?: DependencyList,
): MergeableStore| Type | Description | |
|---|---|---|
create | () => MergeableStore | |
createDeps? | DependencyList | |
| returns | MergeableStore |
Since
v1.0.0
useCreateStore
The useCreateStore hook is used to create a Store within a React application with convenient memoization.
useCreateStore(
create: () => Store,
createDeps?: DependencyList,
): Store| Type | Description | |
|---|---|---|
create | () => Store | A function for performing the creation of the |
createDeps? | DependencyList | An optional array of dependencies for the |
| returns | Store | A reference to the |
It is possible to create a Store outside of the React app with the regular createStore function and pass it in, but you may prefer to create it within the app, perhaps inside the top-level component. To prevent a new Store being created every time the app renders or re-renders, the useCreateStore hook wraps the creation in a memoization.
The useCreateStore hook is a very thin wrapper around the React useMemo hook, defaulting to an empty array for its dependencies, so that by default, the creation only occurs once.
If your create function contains other dependencies, the changing of which should cause the Store to be recreated, you can provide them in an array in the optional second parameter, just as you would for any React hook with dependencies.
Examples
This example creates an empty Store at the top level of a React application. Even though the App component is rendered twice, the Store creation only occurs once by default.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useCreateStore} from 'tinybase/ui-react';
const App = () => {
const store = useCreateStore(() => {
console.log('Store created');
return createStore().setTables({pets: {fido: {species: 'dog'}}});
});
return <span>{store.getCell('pets', 'fido', 'species')}</span>;
};
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
// -> 'Store created'
root.render(<App />);
// No second Store creation
console.log(app.innerHTML);
// -> '<span>dog</span>'
This example creates an empty Store at the top level of a React application. The App component is rendered twice, each with a different top-level prop. The useCreateStore hook takes the fidoSpecies prop as a dependency, and so the Store is created again on the second render.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useCreateStore} from 'tinybase/ui-react';
const App = ({fidoSpecies}) => {
const store = useCreateStore(() => {
console.log(`Store created for fido as ${fidoSpecies}`);
return createStore().setTables({pets: {fido: {species: fidoSpecies}}});
}, [fidoSpecies]);
return <span>{store.getCell('pets', 'fido', 'species')}</span>;
};
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App fidoSpecies="dog" />);
// -> 'Store created for fido as dog'
console.log(app.innerHTML);
// -> '<span>dog</span>'
root.render(<App fidoSpecies="cat" />);
// -> 'Store created for fido as cat'
console.log(app.innerHTML);
// -> '<span>cat</span>'
Since
v1.0.0
useProvideStore
The useProvideStore hook is used to add a Store object by Id to a Provider component, but imperatively from a component within it.
useProvideStore(
storeId: string,
store: Store,
): void| Type | Description | |
|---|---|---|
storeId | string | The |
store | Store | The |
| returns | void | This has no return value. |
Normally you will register a Store by Id in a context by using the storesById prop of the top-level Provider component. This hook, however, lets you dynamically add a new Store to the context, from within a descendent component. This is useful for applications where the set of Stores is not known at the time of the first render of the root Provider.
A Store added to the Provider context in this way will be available to other components within the context (using the useStore hook and so on). If you use the same Id as an existing Store registration, the new one will take priority over one provided by the storesById prop.
Note that other components that consume a Store registered like this should defend against it being undefined at first. On the first render, the other component will likely not yet have completed the registration. In the example below, we use the null-safe useStore('petStore')? to do this.
Example
This example creates a Provider context. A child component registers a Store into it which is then consumable by a peer child component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {
Provider,
useCreateStore,
useProvideStore,
useStore,
} from 'tinybase/ui-react';
const App = () => (
<Provider>
<RegisterStore />
<ConsumeStore />
</Provider>
);
const RegisterStore = () => {
const store = useCreateStore(() =>
createStore().setCell('pets', 'fido', 'color', 'brown'),
);
useProvideStore('petStore', store);
return null;
};
const ConsumeStore = () => (
<span>{JSON.stringify(useStore('petStore')?.getTableIds())}</span>
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
console.log(app.innerHTML);
// -> '<span>["pets"]</span>'
Since
v4.4.2
useStore
The useStore hook is used to get a reference to a Store from within a Provider component context.
useStore(id?: string): Store | undefined| Type | Description | |
|---|---|---|
id? | string | An optional |
| returns | Store | undefined | A reference to the |
A Provider component is used to wrap part of an application in a context. It can contain a default Store (or a set of Store objects named by Id) that can be easily accessed without having to be passed down as props through every component.
The useStore hook lets you either get a reference to the default Store (when called without a parameter), or one of the Store objects that are named by Id (when called with an Id parameter).
Examples
This example creates a Provider context into which a default Store is provided. A component within it then uses the useStore hook to get a reference to the Store again, without the need to have it passed as a prop.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useStore} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{useStore().getListenerStats().tables}</span>;
const store = createStore();
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useStore hook with that Id to get a reference to the Store again, without the need to have it passed as a prop.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useStore} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useStore('petStore').getListenerStats().tables}</span>
);
const store = createStore();
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
Since
v1.0.0
useStoreIds
The useStoreIds hook is used to retrieve the Ids of all the named Store objects present in the current Provider component context.
useStoreIds(): IdsExample
This example adds two named Store objects to a Provider context and an inner component accesses their Ids.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useCreateStore, useStoreIds} from 'tinybase/ui-react';
const App = () => {
const store1 = useCreateStore(createStore);
const store2 = useCreateStore(createStore);
return (
<Provider storesById={{store1, store2}}>
<Pane />
</Provider>
);
};
const Pane = () => <span>{JSON.stringify(useStoreIds())}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["store1","store2"]</span>'
Since
v4.1.0
useStoreOrStoreById
The useStoreOrStoreById hook is used to get a reference to a Store object from within a Provider component context, or have it passed directly to this hook.
useStoreOrStoreById(storeOrStoreId?: StoreOrStoreId): Store | undefined| Type | Description | |
|---|---|---|
storeOrStoreId? | StoreOrStoreId | Either an |
| returns | Store | undefined | A reference to the |
This is mostly of use when you are developing a component that needs a Store object and which might have been passed in explicitly to the component or is to be picked up from the context by Id (a common pattern for Store-based components).
This is unlikely to be used often. For most situations, you will want to use the useStore hook.
Example
This example creates a Provider context into which a default Store object is provided. A component within it then uses the useStoreOrStoreById hook to get a reference to the Store object again, without the need to have it passed as a prop. Note however, that unlike the useStore hook example, this component would also work if you were to pass the Store object directly into it, making it more portable.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useStoreOrStoreById} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = ({store}) => (
<span>{JSON.stringify(useStoreOrStoreById(store).getTableIds())}</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>["pets"]</span>'
Since
v4.1.0
useStores
The useStores hook is used to get a reference to all the Store objects named by Id within a Provider component context.
useStores(): {[storeId: Id]: Store}A Provider component is used to wrap part of an application in a context. It can contain a default Store (or a set of Store objects named by Id) that can be easily accessed without having to be passed down as props through every component.
The useStores hook lets you get a reference to the latter as an object.
Example
This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useStores hook to get a reference to the Store again.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useStores} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useStores()['petStore'].getListenerStats().tables}</span>
);
const store = createStore();
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
Since
v5.4.1
useDelTablesCallback
The useDelTablesCallback hook returns a callback that can be used to remove all of the tabular data in a Store.
useDelTablesCallback(
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store) => void,
thenDeps?: DependencyList,
): Callback| Type | Description | |
|---|---|---|
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store) => void | A function which is called after the deletion, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
| returns | Callback | A callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will delete data in the Store.
For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the deletion to your application's undo stack.
The Store to which the callback will make the deletion (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useDelTablesCallback hook to create an event handler which deletes from the Store when the span element is clicked.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useDelTablesCallback, useTables} from 'tinybase/ui-react';
const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const App = () => {
const handleClick = useDelTablesCallback(store, () =>
console.log('Deleted'),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useTables(store))}
</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"pets":{"nemo":{"species":"fish"}}}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Deleted'
console.log(span.innerHTML);
// -> '{}'
Since
v1.0.0
useHasTables
The useHasTables hook returns a boolean indicating whether any Table objects exist in the Store, and registers a listener so that any changes to that result will cause a re-render.
useHasTables(storeOrStoreId?: StoreOrStoreId): boolean| Type | Description | |
|---|---|---|
storeOrStoreId? | StoreOrStoreId | The |
| returns | boolean | Whether any |
A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useHasTables hook lets you indicate which Store to get data for: omit the optional parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Tables will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store outside the application, which is used in the useHasTables hook by reference. A change to the data in the Store re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useHasTables} from 'tinybase/ui-react';
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => <span>{JSON.stringify(useHasTables(store))}</span>;
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
console.log(app.innerHTML);
// -> '<span>true</span>'
store.delTable('pets');
console.log(app.innerHTML);
// -> '<span>false</span>'
This example creates a Provider context into which a default Store is provided. A component within it then uses the useHasTables hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useHasTables} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useHasTables())}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>true</span>'
This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useHasTables hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useHasTables} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useHasTables('petStore'))}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>true</span>'
Since
v4.4.0
useHasTablesListener
The useHasTablesListener hook registers a listener function with the Store that will be called when Tables as a whole are added to or removed from the Store.
useHasTablesListener(
listener: HasTablesListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
listener | HasTablesListener | The function that will be called whenever |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useHasTables hook).
Unlike the addHasTablesListener method, which returns a listener Id and requires you to remove it manually, the useHasTablesListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.
Example
This example uses the useHasTablesListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useHasTablesListener} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useHasTablesListener(() => console.log('Tables existence changed'));
return <span>App</span>;
};
const store = createStore();
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().hasTables);
// -> 1
store.setCell('pets', 'fido', 'color', 'brown');
// -> 'Tables existence changed'
root.unmount();
console.log(store.getListenerStats().hasTables);
// -> 0
Since
v4.4.0
useSetTablesCallback
The useSetTablesCallback hook returns a parameterized callback that can be used to set the tabular data of a Store.
useSetTablesCallback<Parameter>(
getTables: (parameter: Parameter, store: Store) => Tables,
getTablesDeps?: DependencyList,
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store, tables: Tables) => void,
thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>| Type | Description | |
|---|---|---|
getTables | (parameter: Parameter, store: Store) => Tables | A function which returns the |
getTablesDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store, tables: Tables) => void | A function which is called after the mutation, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
| returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will mutate the data in the Store. In this case, the parameter will likely be the event, so that you can use data from it as part of the mutation.
The first parameter is a function which will produce the Tables object that will then be used to update the Store in the callback.
If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional second parameter, just as you would for any React hook with dependencies.
For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the mutation to your application's undo stack.
The Store to which the callback will make the mutation (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useSetTablesCallback hook to create an event handler which updates the Store when the span element is clicked.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useSetTablesCallback, useTables} from 'tinybase/ui-react';
const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const App = () => {
const handleClick = useSetTablesCallback(
(e) => ({pets: {nemo: {species: 'fish', bubbles: e.bubbles}}}),
[],
store,
(store, tables) => console.log(`Updated: ${JSON.stringify(tables)}`),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useTables(store))}
</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"pets":{"nemo":{"species":"fish"}}}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Updated: {"pets":{"nemo":{"species":"fish","bubbles":true}}}'
console.log(span.innerHTML);
// -> '{"pets":{"nemo":{"species":"fish","bubbles":true}}}'
Since
v1.0.0
useTables
The useTables hook returns a Tables object containing the tabular data of a Store, and registers a listener so that any changes to that result will cause a re-render.
useTables(storeOrStoreId?: StoreOrStoreId): Tables| Type | Description | |
|---|---|---|
storeOrStoreId? | StoreOrStoreId | The |
| returns | Tables |
A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useTables hook lets you indicate which Store to get data for: omit the optional parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Tables will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store outside the application, which is used in the useTables hook by reference. A change to the data in the Store re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useTables} from 'tinybase/ui-react';
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => <span>{JSON.stringify(useTables(store))}</span>;
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
console.log(app.innerHTML);
// -> '<span>{"pets":{"fido":{"color":"brown"}}}</span>'
store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<span>{"pets":{"fido":{"color":"walnut"}}}</span>'
This example creates a Provider context into which a default Store is provided. A component within it then uses the useTables hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useTables} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useTables())}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>{"pets":{"fido":{"color":"brown"}}}</span>'
This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useTables hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useTables} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useTables('petStore'))}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>{"pets":{"fido":{"color":"brown"}}}</span>'
Since
v1.0.0
useTablesListener
The useTablesListener hook registers a listener function with a Store that will be called whenever tabular data in it changes.
useTablesListener(
listener: TablesListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
listener | TablesListener | The function that will be called whenever tabular data in the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useTables hook).
Unlike the addTablesListener method, which returns a listener Id and requires you to remove it manually, the useTablesListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.
Example
This example uses the useTablesListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useTablesListener} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useTablesListener(() => console.log('Tables changed'));
return <span>App</span>;
};
const store = createStore().setTables({pets: {fido: {color: 'brown'}}});
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().tables);
// -> 1
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Tables changed'
root.unmount();
console.log(store.getListenerStats().tables);
// -> 0
Since
v1.0.0
useTableIds
The useTableIds hook returns the Ids of every Table in a Store, and registers a listener so that any changes to that result will cause a re-render.
useTableIds(storeOrStoreId?: StoreOrStoreId): Ids| Type | Description | |
|---|---|---|
storeOrStoreId? | StoreOrStoreId | The |
| returns | Ids |
A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useTableIds hook lets you indicate which Store to get data for: omit the optional parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Table Ids will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store outside the application, which is used in the useTableIds hook by reference. A change to the data in the Store re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useTableIds} from 'tinybase/ui-react';
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => <span>{JSON.stringify(useTableIds(store))}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["pets"]</span>'
store.setCell('species', 'dog', 'price', 5);
console.log(app.innerHTML);
// -> '<span>["pets","species"]</span>'
This example creates a Provider context into which a default Store is provided. A component within it then uses the useTableIds hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useTableIds} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useTableIds())}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>["pets"]</span>'
This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useTableIds hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useTableIds} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useTableIds('petStore'))}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>["pets"]</span>'
Since
v1.0.0
useTableIdsListener
The useTableIdsListener hook registers a listener function with a Store that will be called whenever the Table Ids in it change.
useTableIdsListener(
listener: TableIdsListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
listener | TableIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useTableIds hook).
Unlike the addTableIdsListener method, which returns a listener Id and requires you to remove it manually, the useTableIdsListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.
Example
This example uses the useTableIdsListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useTableIdsListener} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useTableIdsListener(() => console.log('Table Ids changed'));
return <span>App</span>;
};
const store = createStore().setTables({pets: {fido: {color: 'brown'}}});
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().tableIds);
// -> 1
store.setTable('species', {dog: {price: 5}});
// -> 'Table Ids changed'
root.unmount();
console.log(store.getListenerStats().tableIds);
// -> 0
Since
v1.0.0
useDelTableCallback
The useDelTableCallback hook returns a parameterized callback that can be used to remove a single Table from a Store.
useDelTableCallback<Parameter>(
tableId: string | GetId<Parameter>,
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store) => void,
thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>| Type | Description | |
|---|---|---|
tableId | string | GetId<Parameter> | The |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store) => void | A function which is called after the deletion, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
| returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will delete data in the Store. In this case, the parameter will likely be the event, so that you can use data from it as part of the deletion.
For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the deletion to your application's undo stack.
The Store to which the callback will make the deletion (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useDelTableCallback hook to create an event handler which deletes from the Store when the span element is clicked.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useDelTableCallback, useTables} from 'tinybase/ui-react';
const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const App = () => {
const handleClick = useDelTableCallback('pets', store, () =>
console.log('Deleted'),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useTables(store))}
</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"pets":{"nemo":{"species":"fish"}}}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Deleted'
console.log(span.innerHTML);
// -> '{}'
Since
v1.0.0
useHasTable
The useHasTable hook returns a boolean indicating whether a given Table exists in the Store, and registers a listener so that any changes to that result will cause a re-render.
useHasTable(
tableId: string,
storeOrStoreId?: StoreOrStoreId,
): boolean| Type | Description | |
|---|---|---|
tableId | string | |
storeOrStoreId? | StoreOrStoreId | The |
| returns | boolean |
A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useHasTable hook lets you indicate which Store to get data for: omit the optional parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Table will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store outside the application, which is used in the useHasTable hook by reference. A change to the data in the Store re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useHasTable} from 'tinybase/ui-react';
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => (
<span>{JSON.stringify(useHasTable('pets', store))}</span>
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
console.log(app.innerHTML);
// -> '<span>true</span>'
store.delTable('pets');
console.log(app.innerHTML);
// -> '<span>false</span>'
This example creates a Provider context into which a default Store is provided. A component within it then uses the useHasTable hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useHasTable} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useHasTable('pets'))}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>true</span>'
This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useHasTable hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useHasTable} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useHasTable('pets', 'petStore'))}</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>true</span>'
Since
v4.4.0
useHasTableCell
The useHasTableCell hook returns a boolean indicating whether a given Cell exists anywhere in a Table, not just in a specific Row, and registers a listener so that any changes to that result will cause a re-render.
useHasTableCell(
tableId: string,
cellId: string,
storeOrStoreId?: StoreOrStoreId,
): boolean| Type | Description | |
|---|---|---|
tableId | string | |
cellId | string | |
storeOrStoreId? | StoreOrStoreId | The |
| returns | boolean |
A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useHasTableCell hook lets you indicate which Store to get data for: omit the optional parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Table will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store outside the application, which is used in the useHasTableCell hook by reference. A change to the data in the Store re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useHasTableCell} from 'tinybase/ui-react';
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => (
<span>{JSON.stringify(useHasTableCell('pets', 'legs', store))}</span>
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
console.log(app.innerHTML);
// -> '<span>false</span>'
store.setRow('pets', 'felix', {color: 'black', legs: 4});
console.log(app.innerHTML);
// -> '<span>true</span>'
This example creates a Provider context into which a default Store is provided. A component within it then uses the useHasTableCell hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useHasTableCell} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useHasTableCell('pets', 'legs'))}</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>false</span>'
This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useHasTableCell hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useHasTableCell} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{JSON.stringify(useHasTableCell('pets', 'legs', 'petStore'))}
</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>false</span>'
Since
v4.4.0
useHasTableCellListener
The useHasTableCellListener hook registers a listener function with the Store that will be called when a Cell is added to or removed from anywhere in a Table as a whole.
useHasTableCellListener(
tableId: IdOrNull,
cellId: IdOrNull,
listener: HasTableCellListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
cellId | IdOrNull | |
listener | HasTableCellListener | The function that will be called whenever the matching |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useHasTableCell hook).
You can either listen to a single Table Cell being added or removed (by specifying the Table Id and Cell Id, as the method's first two parameters) or changes to any Table Cell (by providing null wildcards).
Unlike the addHasTableCellIds method, which returns a listener Id and requires you to remove it manually, the useHasTableCellListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.
Example
This example uses the useHasTableCellListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useHasTableCellListener} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useHasTableCellListener('pets', 'color', () =>
console.log('Table Cell existence changed'),
);
return <span>App</span>;
};
const store = createStore();
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().hasTableCell);
// -> 1
store.setRow('pets', 'fido', {color: 'brown'});
// -> 'Table Cell existence changed'
root.unmount();
console.log(store.getListenerStats().hasTableCell);
// -> 0
Since
v4.4.0
useHasTableListener
The useHasTableListener hook registers a listener function with the Store that will be called when a Table is added to or removed from the Store.
useHasTableListener(
tableId: IdOrNull,
listener: HasTableListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
listener | HasTableListener | The function that will be called whenever the matching |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useHasTable hook).
You can either listen to a single Table (by specifying its Id as the method's first parameter) or changes to any Table (by providing a null wildcard).
Unlike the addHasTableListener method, which returns a listener Id and requires you to remove it manually, the useHasTableListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.
Example
This example uses the useHasTableListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useHasTableListener} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useHasTableListener('pets', () =>
console.log('Table existence changed'),
);
return <span>App</span>;
};
const store = createStore();
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().hasTable);
// -> 1
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Table existence changed'
root.unmount();
console.log(store.getListenerStats().hasTable);
// -> 0
Since
v4.4.0
useSetTableCallback
The useSetTableCallback hook returns a parameterized callback that can be used to set the data of a single Table in a Store.
useSetTableCallback<Parameter>(
tableId: string | GetId<Parameter>,
getTable: (parameter: Parameter, store: Store) => Table,
getTableDeps?: DependencyList,
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store, table: Table) => void,
thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>| Type | Description | |
|---|---|---|
tableId | string | GetId<Parameter> | The |
getTable | (parameter: Parameter, store: Store) => Table | A function which returns the |
getTableDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store, table: Table) => void | A function which is called after the mutation, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
| returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will mutate the data in the Store. In this case, the parameter will likely be the event, so that you can use data from it as part of the mutation.
The second parameter is a function which will produce the Table object that will then be used to update the Store in the callback.
If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional third parameter, just as you would for any React hook with dependencies.
For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the mutation to your application's undo stack.
The Store to which the callback will make the mutation (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useSetTableCallback hook to create an event handler which updates the Store when the span element is clicked.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useSetTableCallback, useTable} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {nemo: {species: 'fish'}});
const App = () => {
const handleClick = useSetTableCallback(
'pets',
(e) => ({nemo: {species: 'fish', bubbles: e.bubbles}}),
[],
store,
(store, table) => console.log(`Updated: ${JSON.stringify(table)}`),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useTable('pets', store))}
</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"nemo":{"species":"fish"}}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Updated: {"nemo":{"species":"fish","bubbles":true}}'
console.log(span.innerHTML);
// -> '{"nemo":{"species":"fish","bubbles":true}}'
Since
v1.0.0
useTable
The useTable hook returns an object containing the data of a single Table in a Store, and registers a listener so that any changes to that result will cause a re-render.
useTable(
tableId: string,
storeOrStoreId?: StoreOrStoreId,
): Table| Type | Description | |
|---|---|---|
tableId | string | |
storeOrStoreId? | StoreOrStoreId | The |
| returns | Table | An object containing the entire data of the |
A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useTable hook lets you indicate which Store to get data for: omit the final optional final parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Table will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store outside the application, which is used in the useTable hook by reference. A change to the data in the Store re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useTable} from 'tinybase/ui-react';
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => <span>{JSON.stringify(useTable('pets', store))}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>{"fido":{"color":"brown"}}</span>'
store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<span>{"fido":{"color":"walnut"}}</span>'
This example creates a Provider context into which a default Store is provided. A component within it then uses the useTable hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useTable} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useTable('pets'))}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>{"fido":{"color":"brown"}}</span>'
This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useTable hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useTable} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useTable('pets', 'petStore'))}</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>{"fido":{"color":"brown"}}</span>'
Since
v1.0.0
useTableCellIds
The useTableCellIds hook returns the Ids of every Cell used across the whole Table, and registers a listener so that any changes to that result will cause a re-render.
useTableCellIds(
tableId: string,
storeOrStoreId?: StoreOrStoreId,
): Ids| Type | Description | |
|---|---|---|
tableId | string | |
storeOrStoreId? | StoreOrStoreId | The |
| returns | Ids | An array of the |
A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useTableCellIds hook lets you indicate which Store to get data for: omit the optional final parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Table Cell Ids will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store outside the application, which is used in the useTableCellIds hook by reference. A change to the data in the Store re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useTableCellIds} from 'tinybase/ui-react';
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => (
<span>{JSON.stringify(useTableCellIds('pets', store))}</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["color"]</span>'
store.setCell('pets', 'felix', 'species', 'cat');
console.log(app.innerHTML);
// -> '<span>["color","species"]</span>'
This example creates a Provider context into which a default Store is provided. A component within it then uses the useTableCellIds hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useTableCellIds} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useTableCellIds('pets'))}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>["color"]</span>'
This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useTableCellIds hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useTableCellIds} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useTableCellIds('pets', 'petStore'))}</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>["color"]</span>'
Since
v3.3.0
useTableCellIdsListener
The useTableCellIdsListener hook registers a listener function with a Store that will be called whenever the Cell Ids that appear anywhere in a Table change.
useTableCellIdsListener(
tableId: IdOrNull,
listener: TableCellIdsListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
listener | TableCellIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useTableCellIds hook).
You can either listen to a single Table (by specifying its Id as the method's first parameter) or changes to any Table (by providing null).
Unlike the addTableCellIdsListener method, which returns a listener Id and requires you to remove it manually, the useTableCellIdsListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.
Example
This example uses the useTableCellIdsListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useTableCellIdsListener} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useTableCellIdsListener('pets', () => console.log('Cell Ids changed'));
return <span>App</span>;
};
const store = createStore().setTables({pets: {fido: {color: 'brown'}}});
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().tableCellIds);
// -> 1
store.setRow('pets', 'felix', {species: 'cat'});
// -> 'Cell Ids changed'
root.unmount();
console.log(store.getListenerStats().rowIds);
// -> 0
Since
v1.0.0
useTableListener
The useTableListener hook registers a listener function with a Store that will be called whenever data in a Table changes.
useTableListener(
tableId: IdOrNull,
listener: TableListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
listener | TableListener | The function that will be called whenever data in the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useTable hook).
You can either listen to a single Table (by specifying its Id as the method's first parameter) or changes to any Table (by providing a null wildcard).
Unlike the addTableListener method, which returns a listener Id and requires you to remove it manually, the useTableListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.
Example
This example uses the useTableListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useTableListener} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useTableListener('pets', () => console.log('Table changed'));
return <span>App</span>;
};
const store = createStore().setTables({pets: {fido: {color: 'brown'}}});
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().table);
// -> 1
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Table changed'
root.unmount();
console.log(store.getListenerStats().table);
// -> 0
Since
v1.0.0
useRowIds
The useRowIds hook returns the Ids of every Row in a given Table, and registers a listener so that any changes to that result will cause a re-render.
useRowIds(
tableId: string,
storeOrStoreId?: StoreOrStoreId,
): Ids| Type | Description | |
|---|---|---|
tableId | string | |
storeOrStoreId? | StoreOrStoreId | The |
| returns | Ids |
A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useRowIds hook lets you indicate which Store to get data for: omit the optional final parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Row Ids will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store outside the application, which is used in the useRowIds hook by reference. A change to the data in the Store re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useRowIds} from 'tinybase/ui-react';
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => <span>{JSON.stringify(useRowIds('pets', store))}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["fido"]</span>'
store.setCell('pets', 'felix', 'color', 'black');
console.log(app.innerHTML);
// -> '<span>["fido","felix"]</span>'
This example creates a Provider context into which a default Store is provided. A component within it then uses the useRowIds hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useRowIds} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useRowIds('pets'))}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>["fido"]</span>'
This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useRowIds hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useRowIds} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useRowIds('pets', 'petStore'))}</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>["fido"]</span>'
Since
v1.0.0
useRowIdsListener
The useRowIdsListener hook registers a listener function with a Store that will be called whenever the Row Ids in a Table change.
useRowIdsListener(
tableId: IdOrNull,
listener: RowIdsListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
listener | RowIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useRowIds hook).
You can either listen to a single Table (by specifying its Id as the method's first parameter) or changes to any Table (by providing null).
Unlike the addRowIdsListener method, which returns a listener Id and requires you to remove it manually, the useRowIdsListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.
Example
This example uses the useRowIdsListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useRowIdsListener} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useRowIdsListener('pets', () => console.log('Row Ids changed'));
return <span>App</span>;
};
const store = createStore().setTables({pets: {fido: {color: 'brown'}}});
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().rowIds);
// -> 1
store.setRow('pets', 'felix', {color: 'black'});
// -> 'Row Ids changed'
root.unmount();
console.log(store.getListenerStats().rowIds);
// -> 0
Since
v1.0.0
useSortedRowIds
The useSortedRowIds hook returns the sorted (and optionally, paginated) Ids of every Row in a given Table, and registers a listener so that any changes to that result will cause a re-render.
useSortedRowIds(
tableId: string,
cellId?: string,
descending?: boolean,
offset?: number,
limit?: number,
storeOrStoreId?: StoreOrStoreId,
): Ids| Type | Description | |
|---|---|---|
tableId | string | |
cellId? | string | The |
descending? | boolean | Whether the sorting should be in descending order. |
offset? | number | The number of |
limit? | number | The maximum number of |
storeOrStoreId? | StoreOrStoreId | The |
| returns | Ids |
A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useSortedRowIds hook lets you indicate which Store to get data for: omit the optional final parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.
When first rendered, this hook will create a listener so that changes to the sorted Row Ids will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store outside the application, which is used in the useSortedRowIds hook by reference. A change to the data in the Store re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useSortedRowIds} from 'tinybase/ui-react';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
const App = () => (
<span>
{JSON.stringify(
useSortedRowIds('pets', 'species', false, 0, undefined, store),
)}
</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["felix","fido"]</span>'
store.setRow('pets', 'cujo', {species: 'wolf'});
console.log(app.innerHTML);
// -> '<span>["felix","fido","cujo"]</span>'
This example creates a Provider context into which a default Store is provided. A component within it then uses the useSortedRowIds hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useSortedRowIds} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useSortedRowIds('pets'))}</span>;
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>["felix","fido"]</span>'
This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useSortedRowIds hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useSortedRowIds} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{JSON.stringify(
useSortedRowIds('pets', 'species', false, 0, undefined, 'petStore'),
)}
</span>
);
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>["felix","fido"]</span>'
Since
v2.0.0
When called with an object as the first argument, the useSortedRowIds method destructures it to make it easier to skip optional parameters.
useSortedRowIds(
args: SortedRowIdsArgs,
storeOrStoreId?: StoreOrStoreId,
): Ids| Type | Description | |
|---|---|---|
args | SortedRowIdsArgs | A |
storeOrStoreId? | StoreOrStoreId | The |
| returns | Ids |
Example
This example creates a Store outside the application, which is used in the useSortedRowIds hook by reference. A change to the data in the Store re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useSortedRowIds} from 'tinybase/ui-react';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
const App = () => (
<span>
{JSON.stringify(
useSortedRowIds({tableId: 'pets', cellId: 'species'}, store),
)}
</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["felix","fido"]</span>'
store.setRow('pets', 'cujo', {species: 'wolf'});
console.log(app.innerHTML);
// -> '<span>["felix","fido","cujo"]</span>'
Since
v6.1.0
useSortedRowIdsListener
The useSortedRowIdsListener hook registers a listener function with a Store that will be called whenever sorted (and optionally, paginated) Row Ids in a Table change.
useSortedRowIdsListener(
tableId: string,
cellId: undefined | string,
descending: boolean,
offset: number,
limit: undefined | number,
listener: SortedRowIdsListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
tableId | string | |
cellId | undefined | string | The |
descending | boolean | Whether the sorting should be in descending order. |
offset | number | The number of |
limit | undefined | number | The maximum number of |
listener | SortedRowIdsListener | The function that will be called whenever the sorted |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useSortedRowIds hook).
Unlike the addSortedRowIdsListener method, which returns a listener Id and requires you to remove it manually, the useSortedRowIdsListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.
Example
This example uses the useSortedRowIdsListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useSortedRowIdsListener} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useSortedRowIdsListener('pets', 'species', false, 0, undefined, () =>
console.log('Sorted Row Ids changed'),
);
return <span>App</span>;
};
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().sortedRowIds);
// -> 1
store.setRow('pets', 'cujo', {species: 'wolf'});
// -> 'Sorted Row Ids changed'
root.unmount();
console.log(store.getListenerStats().sortedRowIds);
// -> 0
Since
v2.0.0
When called with an object as the first argument, the useSortedRowIds method destructures it to make it easier to skip optional parameters.
useSortedRowIdsListener(
args: SortedRowIdsArgs,
listener: SortedRowIdsListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
args | SortedRowIdsArgs | A |
listener | SortedRowIdsListener | The function that will be called whenever the sorted |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
Example
This example uses the useSortedRowIdsListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useSortedRowIdsListener} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useSortedRowIdsListener({tableId: 'pets', cellId: 'species'}, () =>
console.log('Sorted Row Ids changed'),
);
return <span>App</span>;
};
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().sortedRowIds);
// -> 1
store.setRow('pets', 'cujo', {species: 'wolf'});
// -> 'Sorted Row Ids changed'
root.unmount();
console.log(store.getListenerStats().sortedRowIds);
// -> 0
Since
v6.1.0
useAddRowCallback
The useAddRowCallback hook returns a parameterized callback that can be used to create a new Row in a Store.
useAddRowCallback<Parameter>(
tableId: string | GetId<Parameter>,
getRow: (parameter: Parameter, store: Store) => Row,
getRowDeps?: DependencyList,
storeOrStoreId?: StoreOrStoreId,
then?: (rowId: undefined | string, store: Store, row: Row) => void,
thenDeps?: DependencyList,
reuseRowIds?: boolean,
): ParameterizedCallback<Parameter>| Type | Description | |
|---|---|---|
tableId | string | GetId<Parameter> | The |
getRow | (parameter: Parameter, store: Store) => Row | A function which returns the |
getRowDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
then? | (rowId: undefined | string, store: Store, row: Row) => void | A function which is called after the mutation, with the new |
thenDeps? | DependencyList | An optional array of dependencies for the |
reuseRowIds? | boolean | Whether |
| returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will mutate the data in the Store. In this case, the parameter will likely be the event, so that you can use data from it as part of the mutation.
The second parameter is a function which will produce the Row object that will then be used to update the Store in the callback.
If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional third parameter, just as you would for any React hook with dependencies.
For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the mutation to your application's undo stack.
The Store to which the callback will make the mutation (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.
The reuseRowIds parameter defaults to true, which means that if you delete a Row and then add another, the Id will be re-used - unless you delete the entire Table, in which case all Row Ids will reset. Otherwise, if you specify reuseRowIds to be false, then the Id will be a monotonically increasing string representation of an increasing integer, regardless of any you may have previously deleted.
Example
This example uses the useAddRowCallback hook to create an event handler which updates the Store when the span element is clicked.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useAddRowCallback, useTable} from 'tinybase/ui-react';
const store = createStore().setRow('pets', 'nemo', {species: 'fish'});
const App = () => {
const handleClick = useAddRowCallback(
'pets',
(e) => ({species: 'frog', bubbles: e.bubbles}),
[],
store,
(rowId) => console.log(`Added row: ${rowId}`),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useTable('pets', store))}
</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"nemo":{"species":"fish"}}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Added row: 0'
console.log(span.innerHTML);
// -> '{"0":{"species":"frog","bubbles":true},"nemo":{"species":"fish"}}'
Since
v1.0.0
useDelRowCallback
The useDelRowCallback hook returns a parameterized callback that can be used to remove a single Row from a Table.
useDelRowCallback<Parameter>(
tableId: string | GetId<Parameter>,
rowId: string | GetId<Parameter>,
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store) => void,
thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>| Type | Description | |
|---|---|---|
tableId | string | GetId<Parameter> | The |
rowId | string | GetId<Parameter> | The |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store) => void | A function which is called after the deletion, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
| returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will delete data in the Store. In this case, the parameter will likely be the event, so that you can use data from it as part of the deletion.
For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the deletion to your application's undo stack.
The Store to which the callback will make the deletion (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useDelRowCallback hook to create an event handler which deletes from the Store when the span element is clicked.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useDelRowCallback, useTables} from 'tinybase/ui-react';
const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const App = () => {
const handleClick = useDelRowCallback('pets', 'nemo', store, () =>
console.log('Deleted'),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useTables(store))}
</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"pets":{"nemo":{"species":"fish"}}}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Deleted'
console.log(span.innerHTML);
// -> '{}'
Since
v1.0.0
useHasRow
The useHasRow hook returns a boolean indicating whether a given Row exists in the Store, and registers a listener so that any changes to that result will cause a re-render.
useHasRow(
tableId: string,
rowId: string,
storeOrStoreId?: StoreOrStoreId,
): boolean| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
storeOrStoreId? | StoreOrStoreId | The |
| returns | boolean |
A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useHasRow hook lets you indicate which Store to get data for: omit the optional parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Row will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store outside the application, which is used in the useHasRow hook by reference. A change to the data in the Store re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useHasRow} from 'tinybase/ui-react';
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => (
<span>{JSON.stringify(useHasRow('pets', 'felix', store))}</span>
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
console.log(app.innerHTML);
// -> '<span>false</span>'
store.setCell('pets', 'felix', 'color', 'black');
console.log(app.innerHTML);
// -> '<span>true</span>'
This example creates a Provider context into which a default Store is provided. A component within it then uses the useHasRow hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useHasRow} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useHasRow('pets', 'felix'))}</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>false</span>'
This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useHasRow hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useHasRow} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useHasRow('pets', 'felix', 'petStore'))}</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>false</span>'
Since
v4.4.0
useHasRowListener
The useHasRowListener hook registers a listener function with the Store that will be called when a Row is added to or removed from the Store.
useHasRowListener(
tableId: IdOrNull,
rowId: IdOrNull,
listener: HasRowListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
listener | HasRowListener | The function that will be called whenever the matching |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useHasRow hook).
You can either listen to a single Row being added or removed (by specifying the Table Id and Row Id, as the method's first two parameters) or changes to any Row (by providing null wildcards).
Both, either, or neither of the tableId and rowId parameters can be wildcarded with null. You can listen to a specific Row in a specific Table, any Row in a specific Table, a specific Row in any Table, or any Row in any Table.
Unlike the addHasRowListener method, which returns a listener Id and requires you to remove it manually, the useHasRowListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.
Example
This example uses the useHasRowListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useHasRowListener} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useHasRowListener('pets', 'fido', () =>
console.log('Row existence changed'),
);
return <span>App</span>;
};
const store = createStore();
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().hasRow);
// -> 1
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Row existence changed'
root.unmount();
console.log(store.getListenerStats().hasRow);
// -> 0
Since
v4.4.0
useRow
The useRow hook returns an object containing the data of a single Row in a given Table, and registers a listener so that any changes to that result will cause a re-render.
useRow(
tableId: string,
rowId: string,
storeOrStoreId?: StoreOrStoreId,
): Row| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
storeOrStoreId? | StoreOrStoreId | The |
| returns | Row | An object containing the entire data of the |
A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useRow hook lets you indicate which Store to get data for: omit the final optional final parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Row will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store outside the application, which is used in the useRow hook by reference. A change to the data in the Store re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useRow} from 'tinybase/ui-react';
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => (
<span>{JSON.stringify(useRow('pets', 'fido', store))}</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>{"color":"brown"}</span>'
store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<span>{"color":"walnut"}</span>'
This example creates a Provider context into which a default Store is provided. A component within it then uses the useRow hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useRow} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useRow('pets', 'fido'))}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>{"color":"brown"}</span>'
This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useRow hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useRow} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useRow('pets', 'fido', 'petStore'))}</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>{"color":"brown"}</span>'
Since
v1.0.0
useRowCount
The useRowCount hook returns the count of the Row objects in a given Table, and registers a listener so that any changes to that result will cause a re-render.
useRowCount(
tableId: string,
storeOrStoreId?: StoreOrStoreId,
): number| Type | Description | |
|---|---|---|
tableId | string | |
storeOrStoreId? | StoreOrStoreId | The |
| returns | number |
A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useRowCount hook lets you indicate which Store to get data for: omit the optional final parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.
When first rendered, this hook will create a listener so that changes to the count of Row objects will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store outside the application, which is used in the useRowCount hook by reference. A change to the data in the Store re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useRowCount} from 'tinybase/ui-react';
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => <span>{useRowCount('pets', store)}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>1</span>'
store.setCell('pets', 'felix', 'color', 'black');
console.log(app.innerHTML);
// -> '<span>2</span>'
This example creates a Provider context into which a default Store is provided. A component within it then uses the useRowCount hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useRowCount} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{useRowCount('pets')}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>1</span>'
This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useRowCount hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useRowCount} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => <span>{useRowCount('pets', 'petStore')}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>1</span>'
Since
v4.1.0
useRowCountListener
The useRowCountListener hook registers a listener function with a Store that will be called whenever the count of the Row objects in a Table changes.
useRowCountListener(
tableId: IdOrNull,
listener: RowCountListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
listener | RowCountListener | The function that will be called whenever the count of the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useRowCount hook).
You can either listen to a single Table (by specifying its Id as the method's first parameter) or changes to any Table (by providing null).
Unlike the addRowCountListener method, which returns a listener Id and requires you to remove it manually, the useRowCountListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.
Example
This example uses the useRowCountListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useRowCountListener} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useRowCountListener('pets', () => console.log('Row count changed'));
return <span>App</span>;
};
const store = createStore().setTables({pets: {fido: {color: 'brown'}}});
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().rowCount);
// -> 1
store.setRow('pets', 'felix', {color: 'black'});
// -> 'Row count changed'
root.unmount();
console.log(store.getListenerStats().rowCount);
// -> 0
Since
v4.1.0
useRowListener
The useRowListener hook registers a listener function with a Store that will be called whenever data in a Row changes.
useRowListener(
tableId: IdOrNull,
rowId: IdOrNull,
listener: RowListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
listener | RowListener | The function that will be called whenever data in the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useRow hook).
You can either listen to a single Row (by specifying the Table Id and Row Id as the method's first two parameters) or changes to any Row (by providing null wildcards).
Both, either, or neither of the tableId and rowId parameters can be wildcarded with null. You can listen to a specific Row in a specific Table, any Row in a specific Table, a specific Row in any Table, or any Row in any Table.
Unlike the addRowListener method, which returns a listener Id and requires you to remove it manually, the useRowListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.
Example
This example uses the useRowListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useRowListener} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useRowListener('pets', 'fido', () => console.log('Row changed'));
return <span>App</span>;
};
const store = createStore().setTables({pets: {fido: {color: 'brown'}}});
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().row);
// -> 1
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Row changed'
root.unmount();
console.log(store.getListenerStats().row);
// -> 0
Since
v1.0.0
useSetPartialRowCallback
The useSetPartialRowCallback hook returns a parameterized callback that can be used to set partial data of a single Row in the Store, leaving other Cell values unaffected.
useSetPartialRowCallback<Parameter>(
tableId: string | GetId<Parameter>,
rowId: string | GetId<Parameter>,
getPartialRow: (parameter: Parameter, store: Store) => Row,
getPartialRowDeps?: DependencyList,
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store, partialRow: Row) => void,
thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>| Type | Description | |
|---|---|---|
tableId | string | GetId<Parameter> | The |
rowId | string | GetId<Parameter> | The |
getPartialRow | (parameter: Parameter, store: Store) => Row | A function which returns the partial |
getPartialRowDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store, partialRow: Row) => void | A function which is called after the mutation, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
| returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will mutate the data in Store. In this case, the parameter will likely be the event, so that you can use data from it as part of the mutation.
The third parameter is a function which will produce the partial Row object that will then be used to update the Store in the callback.
If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional fourth parameter, just as you would for any React hook with dependencies.
For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the mutation to your application's undo stack.
The Store to which the callback will make the mutation (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useSetPartialRowCallback hook to create an event handler which updates the Store when the span element is clicked.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useRow, useSetPartialRowCallback} from 'tinybase/ui-react';
const store = createStore().setRow('pets', 'nemo', {species: 'fish'});
const App = () => {
const handleClick = useSetPartialRowCallback(
'pets',
'nemo',
(e) => ({bubbles: e.bubbles}),
[],
store,
(store, partialRow) =>
console.log(`Updated: ${JSON.stringify(partialRow)}`),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useRow('pets', 'nemo', store))}
</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"species":"fish"}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Updated: {"bubbles":true}'
console.log(span.innerHTML);
// -> '{"species":"fish","bubbles":true}'
Since
v1.0.0
useSetRowCallback
The useSetRowCallback hook returns a parameterized callback that can be used to set the data of a single Row in a Store.
useSetRowCallback<Parameter>(
tableId: string | GetId<Parameter>,
rowId: string | GetId<Parameter>,
getRow: (parameter: Parameter, store: Store) => Row,
getRowDeps?: DependencyList,
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store, row: Row) => void,
thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>| Type | Description | |
|---|---|---|
tableId | string | GetId<Parameter> | The |
rowId | string | GetId<Parameter> | The |
getRow | (parameter: Parameter, store: Store) => Row | A function which returns the |
getRowDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store, row: Row) => void | A function which is called after the mutation, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
| returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will mutate the data in the Store. In this case, the parameter will likely be the event, so that you can use data from it as part of the mutation.
The third parameter is a function which will produce the Row object that will then be used to update the Store in the callback.
If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional fourth parameter, just as you would for any React hook with dependencies.
For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the mutation to your application's undo stack.
The Store to which the callback will make the mutation (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useSetRowCallback hook to create an event handler which updates the Store when the span element is clicked.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useRow, useSetRowCallback} from 'tinybase/ui-react';
const store = createStore().setRow('pets', 'nemo', {species: 'fish'});
const App = () => {
const handleClick = useSetRowCallback(
'pets',
'nemo',
(e) => ({species: 'fish', bubbles: e.bubbles}),
[],
store,
(store, row) => console.log(`Updated: ${JSON.stringify(row)}`),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useRow('pets', 'nemo', store))}
</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"species":"fish"}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Updated: {"species":"fish","bubbles":true}'
console.log(span.innerHTML);
// -> '{"species":"fish","bubbles":true}'
Since
v1.0.0
useCellIds
The useCellIds hook returns the Ids of every Cell in a given Row, in a given Table, and registers a listener so that any changes to that result will cause a re-render.
useCellIds(
tableId: string,
rowId: string,
storeOrStoreId?: StoreOrStoreId,
): Ids| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
storeOrStoreId? | StoreOrStoreId | The |
| returns | Ids |
A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useCellIds hook lets you indicate which Store to get data for: omit the optional final parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Cell Ids will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store outside the application, which is used in the useCellIds hook by reference. A change to the data in the Store re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useCellIds} from 'tinybase/ui-react';
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => (
<span>{JSON.stringify(useCellIds('pets', 'fido', store))}</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["color"]</span>'
store.setCell('pets', 'fido', 'species', 'dog');
console.log(app.innerHTML);
// -> '<span>["color","species"]</span>'
This example creates a Provider context into which a default Store is provided. A component within it then uses the useCellIds hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useCellIds} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useCellIds('pets', 'fido'))}</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>["color"]</span>'
This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useCellIds hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useCellIds} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useCellIds('pets', 'fido', 'petStore'))}</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>["color"]</span>'
Since
v1.0.0
useCellIdsListener
The useCellIdsListener hook registers a listener function with a Store that will be called whenever the Cell Ids in a Row change.
useCellIdsListener(
tableId: IdOrNull,
rowId: IdOrNull,
listener: CellIdsListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
listener | CellIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useCellIds hook).
You can either listen to a single Row (by specifying the Table Id and Row Id as the method's first two parameters) or changes to any Row (by providing null wildcards).
Both, either, or neither of the tableId and rowId parameters can be wildcarded with null. You can listen to a specific Row in a specific Table, any Row in a specific Table, a specific Row in any Table, or any Row in any Table.
Unlike the addCellIdsListener method, which returns a listener Id and requires you to remove it manually, the useCellIdsListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.
Example
This example uses the useCellIdsListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useCellIdsListener} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useCellIdsListener('pets', 'fido', () =>
console.log('Cell Ids changed'),
);
return <span>App</span>;
};
const store = createStore().setTables({pets: {fido: {color: 'brown'}}});
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().cellIds);
// -> 1
store.setCell('pets', 'fido', 'species', 'dog');
// -> 'Cell Ids changed'
root.unmount();
console.log(store.getListenerStats().cellIds);
// -> 0
Since
v1.0.0
useCell
The useCell hook returns an object containing the value of a single Cell in a given Row, in a given Table, and registers a listener so that any changes to that result will cause a re-render.
useCell(
tableId: string,
rowId: string,
cellId: string,
storeOrStoreId?: StoreOrStoreId,
): CellOrUndefined| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
storeOrStoreId? | StoreOrStoreId | The |
| returns | CellOrUndefined | The value of the |
A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useCell hook lets you indicate which Store to get data for: omit the final optional final parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Cell will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store outside the application, which is used in the useCell hook by reference. A change to the data in the Store re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useCell} from 'tinybase/ui-react';
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => <span>{useCell('pets', 'fido', 'color', store)}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>brown</span>'
store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<span>walnut</span>'
This example creates a Provider context into which a default Store is provided. A component within it then uses the useCell hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useCell} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{useCell('pets', 'fido', 'color')}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>brown</span>'
This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useCell hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useCell} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useCell('pets', 'fido', 'color', 'petStore')}</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>brown</span>'
Since
v1.0.0
useCellListener
The useCellListener hook registers a listener function with a Store that will be called whenever data in a Cell changes.
useCellListener(
tableId: IdOrNull,
rowId: IdOrNull,
cellId: IdOrNull,
listener: CellListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
cellId | IdOrNull | |
listener | CellListener | The function that will be called whenever data in the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useCell hook).
You can either listen to a single Cell (by specifying the Table Id, Row Id, and Cell Id as the method's first three parameters) or changes to any Cell (by providing null wildcards).
All, some, or none of the tableId, rowId, and cellId parameters can be wildcarded with null. You can listen to a specific Cell in a specific Row in a specific Table, any Cell in any Row in any Table, for example - or every other combination of wildcards.
Unlike the addCellListener method, which returns a listener Id and requires you to remove it manually, the useCellListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.
Example
This example uses the useCellListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useCellListener} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useCellListener('pets', 'fido', 'color', () =>
console.log('Cell changed'),
);
return <span>App</span>;
};
const store = createStore().setTables({pets: {fido: {color: 'brown'}}});
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().cell);
// -> 1
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Cell changed'
root.unmount();
console.log(store.getListenerStats().cell);
// -> 0
Since
v1.0.0
useDelCellCallback
The useDelCellCallback hook returns a parameterized callback that can be used to remove a single Cell from a Row.
useDelCellCallback<Parameter>(
tableId: string | GetId<Parameter>,
rowId: string | GetId<Parameter>,
cellId: string | GetId<Parameter>,
forceDel?: boolean,
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store) => void,
thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>| Type | Description | |
|---|---|---|
tableId | string | GetId<Parameter> | The |
rowId | string | GetId<Parameter> | The |
cellId | string | GetId<Parameter> | The |
forceDel? | boolean | An optional flag to indicate that the whole |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store) => void | A function which is called after the deletion, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
| returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will delete data in the Store. In this case, the parameter will likely be the event, so that you can use data from it as part of the deletion.
For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the deletion to your application's undo stack.
The Store to which the callback will make the deletion (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useDelCellCallback hook to create an event handler which deletes from the Store when the span element is clicked.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useDelCellCallback, useTables} from 'tinybase/ui-react';
const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const App = () => {
const handleClick = useDelCellCallback(
'pets',
'nemo',
'species',
false,
store,
() => console.log('Deleted'),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useTables(store))}
</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"pets":{"nemo":{"species":"fish"}}}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Deleted'
console.log(span.innerHTML);
// -> '{}'
Since
v1.0.0
useHasCell
The useHasCell hook returns a boolean indicating whether a given Cell exists in a given Row in a given Table, and registers a listener so that any changes to that result will cause a re-render.
useHasCell(
tableId: string,
rowId: string,
cellId: string,
storeOrStoreId?: StoreOrStoreId,
): boolean| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
storeOrStoreId? | StoreOrStoreId | The |
| returns | boolean | Whether a |
A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useHasCell hook lets you indicate which Store to get data for: omit the optional parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Cell will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store outside the application, which is used in the useHasCell hook by reference. A change to the data in the Store re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useHasCell} from 'tinybase/ui-react';
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => (
<span>{JSON.stringify(useHasCell('pets', 'fido', 'legs', store))}</span>
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
console.log(app.innerHTML);
// -> '<span>false</span>'
store.setCell('pets', 'fido', 'legs', 4);
console.log(app.innerHTML);
// -> '<span>true</span>'
This example creates a Provider context into which a default Store is provided. A component within it then uses the useHasCell hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useHasCell} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useHasCell('pets', 'fido', 'legs'))}</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>false</span>'
This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useHasCell hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useHasCell} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{JSON.stringify(useHasCell('pets', 'fido', 'legs', 'petStore'))}
</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>false</span>'
Since
v4.4.0
useHasCellListener
The useHasCellListener hook registers a listener function with the Store that will be called when a Cell is added to or removed from the Store.
useHasCellListener(
tableId: IdOrNull,
rowId: IdOrNull,
cellId: IdOrNull,
listener: HasCellListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
cellId | IdOrNull | |
listener | HasCellListener | The function that will be called whenever the matching |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useHasCell hook).
You can either listen to a single Cell being added or removed (by specifying the Table Id, Row Id, and Cell Id as the method's first three parameters) or changes to any Cell (by providing null wildcards).
All, some, or none of the tableId, rowId, and cellId parameters can be wildcarded with null. You can listen to a specific Cell in a specific Row in a specific Table, any Cell in any Row in any Table, for example - or every other combination of wildcards.
Unlike the addHasCellListener method, which returns a listener Id and requires you to remove it manually, the useHasCellListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.
Example
This example uses the useHasCellListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useHasCellListener} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useHasCellListener('pets', 'fido', 'color', () =>
console.log('Cell existence changed'),
);
return <span>App</span>;
};
const store = createStore();
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().hasCell);
// -> 1
store.setCell('pets', 'fido', 'color', 'brown');
// -> 'Cell existence changed'
root.unmount();
console.log(store.getListenerStats().hasCell);
// -> 0
Since
v4.4.0
useSetCellCallback
The useSetCellCallback hook returns a parameterized callback that can be used to set the value of a single Cell in a Store.
useSetCellCallback<Parameter>(
tableId: string | GetId<Parameter>,
rowId: string | GetId<Parameter>,
cellId: string | GetId<Parameter>,
getCell: (parameter: Parameter, store: Store) => Cell | MapCell,
getCellDeps?: DependencyList,
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store, cell: Cell | MapCell) => void,
thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>| Type | Description | |
|---|---|---|
tableId | string | GetId<Parameter> | The |
rowId | string | GetId<Parameter> | The |
cellId | string | GetId<Parameter> | The |
getCell | (parameter: Parameter, store: Store) => Cell | MapCell | A function which returns the |
getCellDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store, cell: Cell | MapCell) => void | A function which is called after the mutation, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
| returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will mutate the data in the Store. In this case, the parameter will likely be the event, so that you can use data from it as part of the mutation.
The fourth parameter is a function which will produce the Cell object that will then be used to update the Store in the callback.
If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional fourth parameter, just as you would for any React hook with dependencies.
For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the mutation to your application's undo stack.
The Store to which the callback will make the mutation (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.
Examples
This example uses the useSetCellCallback hook to create an event handler which updates the Store with a Cell value when the span element is clicked.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useRow, useSetCellCallback} from 'tinybase/ui-react';
const store = createStore().setCell('pets', 'nemo', 'species', 'fish');
const App = () => {
const handleClick = useSetCellCallback(
'pets',
'nemo',
'bubbles',
(e) => e.bubbles,
[],
store,
(store, cell) => console.log(`Updated: ${cell}`),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useRow('pets', 'nemo', store))}
</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"species":"fish"}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Updated: true'
console.log(span.innerHTML);
// -> '{"species":"fish","bubbles":true}'
This example uses the useSetCellCallback hook to create an event handler which updates the Store via a MapCell function when the span element is clicked.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useRow, useSetCellCallback} from 'tinybase/ui-react';
const store = createStore().setCell('pets', 'nemo', 'visits', 1);
const App = () => {
const handleClick = useSetCellCallback(
'pets',
'nemo',
'visits',
(e) => (visits) => visits + (e.bubbles ? 1 : 0),
[],
store,
() => console.log(`Updated with MapCell function`),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useRow('pets', 'nemo', store))}
</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"visits":1}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Updated with MapCell function'
console.log(span.innerHTML);
// -> '{"visits":2}'
Since
v1.0.0
useDelValuesCallback
The useDelValuesCallback hook returns a callback that can be used to remove all of the keyed value data in a Store.
useDelValuesCallback(
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store) => void,
thenDeps?: DependencyList,
): Callback| Type | Description | |
|---|---|---|
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store) => void | A function which is called after the deletion, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
| returns | Callback | A callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will delete data in a Store.
For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the deletion to your application's undo stack.
The Store to which the callback will make the deletion (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useDelValuesCallback hook to create an event handler which deletes from the Store when the span element is clicked.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useDelValuesCallback, useValues} from 'tinybase/ui-react';
const store = createStore().setValues({open: true});
const App = () => {
const handleClick = useDelValuesCallback(store, () =>
console.log('Deleted'),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useValues(store))}
</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"open":true}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Deleted'
console.log(span.innerHTML);
// -> '{}'
Since
v3.0.0
useHasValues
The useHasValues hook returns a boolean indicating whether any Values exist in the Store, and registers a listener so that any changes to that result will cause a re-render.
useHasValues(storeOrStoreId?: StoreOrStoreId): boolean| Type | Description | |
|---|---|---|
storeOrStoreId? | StoreOrStoreId | The |
| returns | boolean | Whether any |
A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useHasValues hook lets you indicate which Store to get data for: omit the optional parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Values will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store outside the application, which is used in the useHasValues hook by reference. A change to the data in the Store re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useHasValues} from 'tinybase/ui-react';
const store = createStore().setValue('open', true);
const App = () => <span>{JSON.stringify(useHasValues(store))}</span>;
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
console.log(app.innerHTML);
// -> '<span>true</span>'
store.delValue('open');
console.log(app.innerHTML);
// -> '<span>false</span>'
This example creates a Provider context into which a default Store is provided. A component within it then uses the useHasValues hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useHasValues} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useHasValues())}</span>;
const store = createStore().setValue('open', true);
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>true</span>'
This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useHasValues hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useHasValues} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useHasValues('petStore'))}</span>;
const store = createStore().setValue('open', true);
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>true</span>'
Since
v4.4.0
useHasValuesListener
The useHasValuesListener hook registers a listener function with the Store that will be called when Values as a whole are added to or removed from the Store.
useHasValuesListener(
listener: HasValuesListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
listener | HasValuesListener | The function that will be called whenever |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useHasValues hook).
Unlike the addHasValuesListener method, which returns a listener Id and requires you to remove it manually, the useHasValuesListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.
Example
This example uses the useHasValuesListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useHasValuesListener} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useHasValuesListener(() => console.log('Values existence changed'));
return <span>App</span>;
};
const store = createStore();
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().hasValues);
// -> 1
store.setValue('open', true);
// -> 'Values existence changed'
root.unmount();
console.log(store.getListenerStats().hasValues);
// -> 0
Since
v4.4.0
useSetPartialValuesCallback
The useSetPartialValuesCallback hook returns a parameterized callback that can be used to set partial Values data in the Store, leaving other Values unaffected.
useSetPartialValuesCallback<Parameter>(
getPartialValues: (parameter: Parameter, store: Store) => Values,
getPartialValuesDeps?: DependencyList,
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store, partialValues: Values) => void,
thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>| Type | Description | |
|---|---|---|
getPartialValues | (parameter: Parameter, store: Store) => Values | A function which returns the partial |
getPartialValuesDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store, partialValues: Values) => void | A function which is called after the mutation, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
| returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will mutate the data in the Store. In this case, the parameter will likely be the event, so that you can use data from it as part of the mutation.
The third parameter is a function which will produce the partial Values object that will then be used to update the Store in the callback.
If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional fourth parameter, just as you would for any React hook with dependencies.
For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the mutation to your application's undo stack.
The Store to which the callback will make the mutation (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useSetPartialValuesCallback hook to create an event handler which updates the Store when the span element is clicked.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useSetPartialValuesCallback, useValues} from 'tinybase/ui-react';
const store = createStore().setValues({open: true});
const App = () => {
const handleClick = useSetPartialValuesCallback(
(e) => ({bubbles: e.bubbles}),
[],
store,
(store, partialValues) =>
console.log(`Updated: ${JSON.stringify(partialValues)}`),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useValues(store))}
</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"open":true}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Updated: {"bubbles":true}'
console.log(span.innerHTML);
// -> '{"open":true,"bubbles":true}'
Since
v3.0.0
useSetValuesCallback
The useSetValuesCallback hook returns a parameterized callback that can be used to set the keyed value data of a Store.
useSetValuesCallback<Parameter>(
getValues: (parameter: Parameter, store: Store) => Values,
getValuesDeps?: DependencyList,
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store, values: Values) => void,
thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>| Type | Description | |
|---|---|---|
getValues | (parameter: Parameter, store: Store) => Values | A function which returns the |
getValuesDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store, values: Values) => void | A function which is called after the mutation, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
| returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will mutate the data in the Store. In this case, the parameter will likely be the event, so that you can use data from it as part of the mutation.
The first parameter is a function which will produce the Values object that will then be used to update the Store in the callback.
If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional second parameter, just as you would for any React hook with dependencies.
For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the mutation to your application's undo stack.
The Store to which the callback will make the mutation (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useSetValuesCallback hook to create an event handler which updates the Store when the span element is clicked.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useSetValuesCallback, useValues} from 'tinybase/ui-react';
const store = createStore().setValues({open: true});
const App = () => {
const handleClick = useSetValuesCallback(
(e) => ({bubbles: e.bubbles}),
[],
store,
(store, values) => console.log(`Updated: ${JSON.stringify(values)}`),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useValues(store))}
</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"open":true}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Updated: {"bubbles":true}'
console.log(span.innerHTML);
// -> '{"bubbles":true}'
Since
v3.0.0
useValues
The useValues hook returns a Values object containing the keyed value data of a Store, and registers a listener so that any changes to that result will cause a re-render.
useValues(storeOrStoreId?: StoreOrStoreId): Values| Type | Description | |
|---|---|---|
storeOrStoreId? | StoreOrStoreId | The |
| returns | Values | A |
A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useValues hook lets you indicate which Store to get data for: omit the optional parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Values will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store outside the application, which is used in the useValues hook by reference. A change to the data in the Store re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useValues} from 'tinybase/ui-react';
const store = createStore().setValue('open', true);
const App = () => <span>{JSON.stringify(useValues(store))}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>{"open":true}</span>'
store.setValue('open', false);
console.log(app.innerHTML);
// -> '<span>{"open":false}</span>'
This example creates a Provider context into which a default Store is provided. A component within it then uses the useValues hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useValues} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useValues())}</span>;
const store = createStore().setValue('open', true);
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>{"open":true}</span>'
This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useValues hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useValues} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useValues('petStore'))}</span>;
const store = createStore().setValue('open', true);
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>{"open":true}</span>'
Since
v3.0.0
useValuesListener
The useValuesListener hook registers a listener function with a Store that will be called whenever keyed value data in it changes.
useValuesListener(
listener: ValuesListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
listener | ValuesListener | The function that will be called whenever keyed value data in the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useValues hook).
Unlike the addValuesListener method, which returns a listener Id and requires you to remove it manually, the useValuesListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.
Example
This example uses the useValuesListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useValuesListener} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useValuesListener(() => console.log('Values changed'));
return <span>App</span>;
};
const store = createStore().setValues({open: true});
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().values);
// -> 1
store.setValue('open', false);
// -> 'Values changed'
root.unmount();
console.log(store.getListenerStats().values);
// -> 0
Since
v3.0.0
useDelValueCallback
The useDelValueCallback hook returns a parameterized callback that can be used to remove a single Value from a Store.
useDelValueCallback<Parameter>(
valueId: string | GetId<Parameter>,
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store) => void,
thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>| Type | Description | |
|---|---|---|
valueId | string | GetId<Parameter> | The |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store) => void | A function which is called after the deletion, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
| returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will delete data in the Store. In this case, the parameter will likely be the event, so that you can use data from it as part of the deletion.
For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the deletion to your application's undo stack.
The Store to which the callback will make the deletion (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useDelValueCallback hook to create an event handler which deletes from the Store when the span element is clicked.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useDelValueCallback, useValues} from 'tinybase/ui-react';
const store = createStore().setValues({open: true, employees: 3});
const App = () => {
const handleClick = useDelValueCallback('open', store, () =>
console.log('Deleted'),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useValues(store))}
</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"open":true,"employees":3}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Deleted'
console.log(span.innerHTML);
// -> '{"employees":3}'
Since
v3.0.0
useHasValue
The useHasValue hook returns a boolean indicating whether a given Value exists in the Store, and registers a listener so that any changes to that result will cause a re-render.
useHasValue(
valueId: string,
storeOrStoreId?: StoreOrStoreId,
): boolean| Type | Description | |
|---|---|---|
valueId | string | |
storeOrStoreId? | StoreOrStoreId | The |
| returns | boolean |
A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useHasValue hook lets you indicate which Store to get data for: omit the optional parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Value will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store outside the application, which is used in the useHasValue hook by reference. A change to the data in the Store re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useHasValue} from 'tinybase/ui-react';
const store = createStore().setValue('open', true);
const App = () => (
<span>{JSON.stringify(useHasValue('employees', store))}</span>
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
console.log(app.innerHTML);
// -> '<span>false</span>'
store.setValue('employees', 3);
console.log(app.innerHTML);
// -> '<span>true</span>'
This example creates a Provider context into which a default Store is provided. A component within it then uses the useHasValue hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useHasValue} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useHasValue('employees'))}</span>;
const store = createStore().setValue('open', true);
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>false</span>'
This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useHasValue hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useHasValue} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useHasValue('employees', 'petStore'))}</span>
);
const store = createStore().setValue('open', true);
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>false</span>'
Since
v4.4.0
useHasValueListener
The useHasValueListener hook registers a listener function with the Store that will be called when a Value is added to or removed from the Store.
useHasValueListener(
valueId: IdOrNull,
listener: HasValueListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
valueId | IdOrNull | |
listener | HasValueListener | The function that will be called whenever the matching |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useHasValue hook).
You can either listen to a single Value being added or removed (by specifying the Value Id) or any Value being added or removed (by providing a null wildcard).
Unlike the addHasValueListener method, which returns a listener Id and requires you to remove it manually, the useHasValueListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.
Example
This example uses the useHasValueListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useHasValueListener} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useHasValueListener('open', () =>
console.log('Value existence changed'),
);
return <span>App</span>;
};
const store = createStore();
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().hasValue);
// -> 1
store.setValue('open', false);
// -> 'Value existence changed'
root.unmount();
console.log(store.getListenerStats().hasValue);
// -> 0
Since
v4.4.0
useSetValueCallback
The useSetValueCallback hook returns a parameterized callback that can be used to set the data of a single Value in a Store.
useSetValueCallback<Parameter>(
valueId: string | GetId<Parameter>,
getValue: (parameter: Parameter, store: Store) => Value | MapValue,
getValueDeps?: DependencyList,
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store, value: Value | MapValue) => void,
thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>| Type | Description | |
|---|---|---|
valueId | string | GetId<Parameter> | The |
getValue | (parameter: Parameter, store: Store) => Value | MapValue | A function which returns the |
getValueDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store, value: Value | MapValue) => void | A function which is called after the mutation, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
| returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will mutate the data in the Store. In this case, the parameter will likely be the event, so that you can use data from it as part of the mutation.
The second parameter is a function which will produce the Value object that will then be used to update the Store in the callback.
If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional third parameter, just as you would for any React hook with dependencies.
For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the mutation to your application's undo stack.
The Store to which the callback will make the mutation (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useSetValueCallback hook to create an event handler which updates the Store when the span element is clicked.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useSetValueCallback, useValues} from 'tinybase/ui-react';
const store = createStore().setValue('open', true);
const App = () => {
const handleClick = useSetValueCallback(
'bubbles',
(e) => e.bubbles,
[],
store,
(store, value) => console.log(`Updated: ${JSON.stringify(value)}`),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useValues(store))}
</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"open":true}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Updated: true'
console.log(span.innerHTML);
// -> '{"open":true,"bubbles":true}'
Since
v3.0.0
useValue
The useValue hook returns an object containing the data of a single Value in a Store, and registers a listener so that any changes to that result will cause a re-render.
useValue(
valueId: string,
storeOrStoreId?: StoreOrStoreId,
): ValueOrUndefined| Type | Description | |
|---|---|---|
valueId | string | |
storeOrStoreId? | StoreOrStoreId | The |
| returns | ValueOrUndefined | An object containing the entire data of the |
A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useValue hook lets you indicate which Store to get data for: omit the final optional final parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Value will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store outside the application, which is used in the useValue hook by reference. A change to the data in the Store re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useValue} from 'tinybase/ui-react';
const store = createStore().setValue('open', true);
const App = () => <span>{JSON.stringify(useValue('open', store))}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>true</span>'
store.setValue('open', false);
console.log(app.innerHTML);
// -> '<span>false</span>'
This example creates a Provider context into which a default Store is provided. A component within it then uses the useValue hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useValue} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useValue('open'))}</span>;
const store = createStore().setValue('open', true);
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>true</span>'
This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useValue hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useValue} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useValue('open', 'petStore'))}</span>
);
const store = createStore().setValue('open', true);
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>true</span>'
Since
v3.0.0
useValueIds
The useValueIds hook returns the Ids of every Value in a Store, and registers a listener so that any changes to that result will cause a re-render.
useValueIds(storeOrStoreId?: StoreOrStoreId): Ids| Type | Description | |
|---|---|---|
storeOrStoreId? | StoreOrStoreId | The |
| returns | Ids |
A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useValueIds hook lets you indicate which Store to get data for: omit the optional parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Value Ids will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store outside the application, which is used in the useValueIds hook by reference. A change to the data in the Store re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useValueIds} from 'tinybase/ui-react';
const store = createStore().setValue('open', true);
const App = () => <span>{JSON.stringify(useValueIds(store))}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["open"]</span>'
store.setValue('employees', 3);
console.log(app.innerHTML);
// -> '<span>["open","employees"]</span>'
This example creates a Provider context into which a default Store is provided. A component within it then uses the useValueIds hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useValueIds} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useValueIds())}</span>;
const store = createStore().setValue('open', true);
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>["open"]</span>'
This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useValueIds hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useValueIds} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useValueIds('petStore'))}</span>;
const store = createStore().setValue('open', true);
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>["open"]</span>'
Since
v3.0.0
useValueIdsListener
The useValueIdsListener hook registers a listener function with a Store that will be called whenever the Value Ids in it change.
useValueIdsListener(
listener: ValueIdsListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
listener | ValueIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useValueIds hook).
Unlike the addValueIdsListener method, which returns a listener Id and requires you to remove it manually, the useValueIdsListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.
Example
This example uses the useValueIdsListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useValueIdsListener} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useValueIdsListener(() => console.log('Value Ids changed'));
return <span>App</span>;
};
const store = createStore().setValues({open: true});
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().valueIds);
// -> 1
store.setValue('employees', 3);
// -> 'Value Ids changed'
root.unmount();
console.log(store.getListenerStats().valueIds);
// -> 0
Since
v3.0.0
useValueListener
The useValueListener hook registers a listener function with a Store that will be called whenever data in a Value changes.
useValueListener(
valueId: IdOrNull,
listener: ValueListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
valueId | IdOrNull | |
listener | ValueListener | The function that will be called whenever data in the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useValue hook).
You can either listen to a single Value (by specifying its Id as the method's first parameter) or changes to any Value (by providing a null wildcard).
Unlike the addValueListener method, which returns a listener Id and requires you to remove it manually, the useValueListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.
Example
This example uses the useValueListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useValueListener} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useValueListener('open', () => console.log('Value changed'));
return <span>App</span>;
};
const store = createStore().setValues({open: true});
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().value);
// -> 1
store.setValue('open', false);
// -> 'Value changed'
root.unmount();
console.log(store.getListenerStats().value);
// -> 0
Since
v3.0.0
useDidFinishTransactionListener
The useDidFinishTransactionListener hook registers a listener function with a Store that will be called just after other non-mutating listeners are called at the end of the transaction.
useDidFinishTransactionListener(
listener: TransactionListener,
listenerDeps?: DependencyList,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
listener | TransactionListener | The function that will be called after the end of a transaction. |
listenerDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
Unlike the addDidFinishTransactionListener method, which returns a listener Id and requires you to remove it manually, the useDidFinishTransactionListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.
Example
This example uses the useDidFinishTransactionListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {
Provider,
useDidFinishTransactionListener,
} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useDidFinishTransactionListener(() =>
console.log('Did finish transaction'),
);
return <span>App</span>;
};
const store = createStore();
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().transaction);
// -> 1
store.setValue('open', false);
// -> 'Did finish transaction'
root.unmount();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v4.2.2
useStartTransactionListener
The useStartTransactionListener hook registers a listener function with the Store that will be called at the start of a transaction.
useStartTransactionListener(
listener: TransactionListener,
listenerDeps?: DependencyList,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
listener | TransactionListener | The function that will be called at the start of a transaction. |
listenerDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
Unlike the addStartTransactionListener method, which returns a listener Id and requires you to remove it manually, the useStartTransactionListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.
Example
This example uses the useStartTransactionListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, useStartTransactionListener} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useStartTransactionListener(() => console.log('Start transaction'));
return <span>App</span>;
};
const store = createStore();
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().transaction);
// -> 1
store.setValue('open', false);
// -> 'Start transaction'
root.unmount();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v4.2.2
useWillFinishTransactionListener
The useWillFinishTransactionListener hook registers a listener function with a Store that will be called just before other non-mutating listeners are called at the end of the transaction.
useWillFinishTransactionListener(
listener: TransactionListener,
listenerDeps?: DependencyList,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
listener | TransactionListener | The function that will be called before the end of a transaction. |
listenerDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
Unlike the addWillFinisTransactionListener method, which returns a listener Id and requires you to remove it manually, the useWillFinishTransactionListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.
Example
This example uses the useWillFinishTransactionListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {
Provider,
useWillFinishTransactionListener,
} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useWillFinishTransactionListener(() =>
console.log('Will finish transaction'),
);
return <span>App</span>;
};
const store = createStore();
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().transaction);
// -> 1
store.setValue('open', false);
// -> 'Will finish transaction'
root.unmount();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v4.2.2
Synchronizer hooks
useCreateSynchronizer
The useCreateSynchronizer hook is used to create a Synchronizer within a React application along with convenient memoization and callbacks.
useCreateSynchronizer<SynchronizerOrUndefined>(
store: undefined | MergeableStore,
create: (store: MergeableStore) => Promise<SynchronizerOrUndefined>,
createDeps?: DependencyList,
destroy?: (synchronizer: Synchronizer) => void,
destroyDeps?: DependencyList,
): SynchronizerOrUndefined| Type | Description | |
|---|---|---|
store | undefined | MergeableStore | A reference to the |
create | (store: MergeableStore) => Promise<SynchronizerOrUndefined> | An asynchronous function for performing the creation steps of the |
createDeps? | DependencyList | An optional array of dependencies for the |
destroy? | (synchronizer: Synchronizer) => void | An optional callback whenever the |
destroyDeps? | DependencyList | An optional array of dependencies for the |
| returns | SynchronizerOrUndefined | A reference to the |
It is possible to create a Synchronizer outside of the React app with the regular createSynchronizer function and pass it in, but you may prefer to create it within the app, perhaps inside the top-level component. To prevent a new Synchronizer being created every time the app renders or re-renders, the useCreateSynchronizer hook performs the creation in an effect.
If your asynchronous create function (the second parameter to the hook) contains dependencies, the changing of which should cause the Synchronizer to be recreated, you can provide them in an array in the third parameter, just as you would for any React hook with dependencies. The MergeableStore passed in as the first parameter of this hook is used as a dependency by default.
The create function can return undefined, meaning that you can enable or disable synchronization conditionally within this hook. This is useful for applications which might turn on or off their cloud synchronization or collaboration features.
This hook ensures the Synchronizer object is destroyed whenever a new one is created or the component is unmounted.
Examples
This example creates a Synchronizer at the top level of a React application. Even though the App component is rendered twice, the Synchronizer creation only occurs once by default.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createMergeableStore} from 'tinybase';
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
import {
useCreateMergeableStore,
useCreateSynchronizer,
useTables,
} from 'tinybase/ui-react';
const App = () => {
const store = useCreateMergeableStore(() => createMergeableStore('s1'));
useCreateSynchronizer(store, async (store) => {
console.log('Synchronizer created');
return await createLocalSynchronizer(store, 'pets');
});
return <span>{JSON.stringify(useTables(store))}</span>;
};
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
// -> 'Synchronizer created'
// ...
root.render(<App />);
// No second Synchronizer creation
root.unmount();
This example creates a Synchronizer at the top level of a React application. The App component is rendered twice, each with a different top-level prop. The useCreateSynchronizer hook takes the url prop as a dependency, and so the Synchronizer object is created again on the second render. The first is destroyed and the destroy parameter is called for it. A then parameter is provided to start both Synchronizers' synchronization.
import React from 'react';
import {WebSocket, WebSocketServer} from 'ws';
import {createRoot} from 'react-dom/client';
import {createMergeableStore} from 'tinybase';
import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';
import {createWsServer} from 'tinybase/synchronizers/synchronizer-ws-server';
import {
useCreateMergeableStore,
useCreateSynchronizer,
useTables,
} from 'tinybase/ui-react';
const server1 = createWsServer(new WebSocketServer({port: 8044}));
const server2 = createWsServer(new WebSocketServer({port: 8045}));
const App = ({url}) => {
const store = useCreateMergeableStore(() => createMergeableStore('s1'));
useCreateSynchronizer(
store,
async (store) => {
const webSocket = new WebSocket(url);
console.log(`Synchronizer created for ${webSocket.url}`);
return await createWsSynchronizer(store, webSocket);
},
[url],
(synchronizer) => {
const webSocket = synchronizer.getWebSocket();
console.log(`Synchronizer destroyed for ${webSocket.url}`);
},
);
return <span>{JSON.stringify(useTables(store))}</span>;
};
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App url="ws://localhost:8044/" />);
// ...
// -> 'Synchronizer created for ws://localhost:8044/'
root.render(<App url="ws://localhost:8045/" />);
// ...
// -> 'Synchronizer created for ws://localhost:8045/'
// -> 'Synchronizer destroyed for ws://localhost:8044/'
root.unmount();
// -> 'Synchronizer destroyed for ws://localhost:8045/'
await server1.destroy();
await server2.destroy();
Since
v5.0.0
useProvideSynchronizer
The useProvideSynchronizer hook is used to add a Synchronizer object by Id to a Provider component, but imperatively from a component within it.
useProvideSynchronizer(
synchronizerId: string,
synchronizer: Synchronizer,
): void| Type | Description | |
|---|---|---|
synchronizerId | string | The |
synchronizer | Synchronizer | The |
| returns | void | This has no return value. |
Normally you will register a Synchronizer object by Id in a context by using the synchronizersById prop of the top-level Provider component. This hook, however, lets you dynamically add a new Synchronizer object to the context, from within a component. This is useful for applications where the set of Synchronizer objects is not known at the time of the first render of the root Provider.
A Synchronizer object added to the Provider context in this way will be available to other components within the context (using the useSynchronizer hook and so on). If you use the same Id as an existing Synchronizer object registration, the new one will take priority over one provided by the synchronizersById prop.
Note that other components that consume a Synchronizer object registered like this should defend against it being undefined at first. On the first render, the other component will likely not yet have completed the registration. In the example below, we use the null-safe useSynchronizer('petSynchronizer')? to do this.
Example
This example creates a Provider context. A child component registers a Synchronizer object into it which is then consumable by a peer child component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createMergeableStore} from 'tinybase';
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
import {
Provider,
useCreateStore,
useCreateSynchronizer,
useProvideSynchronizer,
useSynchronizer,
} from 'tinybase/ui-react';
const App = () => (
<Provider>
<RegisterSynchronizer />
<ConsumeSynchronizer />
</Provider>
);
const RegisterSynchronizer = () => {
const store = useCreateStore(() =>
createMergeableStore().setCell('pets', 'fido', 'color', 'brown'),
);
const synchronizer = useCreateSynchronizer(
store,
createLocalSynchronizer,
);
useProvideSynchronizer('petSynchronizer', synchronizer);
return null;
};
const ConsumeSynchronizer = () => (
<span>{useSynchronizer('petSynchronizer')?.getStatus()}</span>
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
// ...
console.log(app.innerHTML);
// -> '<span>0</span>'
Since
v5.3.0
useSynchronizer
The useSynchronizer hook is used to get a reference to a Synchronizer object from within a Provider component context.
useSynchronizer(id?: string): Synchronizer | undefined| Type | Description | |
|---|---|---|
id? | string | An optional |
| returns | Synchronizer | undefined | A reference to the |
A Provider component is used to wrap part of an application in a context. It can contain a default Synchronizer object (or a set of Synchronizer objects named by Id) that can be easily accessed without having to be passed down as props through every component.
The useSynchronizer hook lets you either get a reference to the default Synchronizer object (when called without a parameter), or one of the Synchronizer objects that are named by Id (when called with an Id parameter).
Examples
This example creates a Provider context into which a default Synchronizer object is provided. A component within it then uses the useSynchronizer hook to get a reference to the Synchronizer object again, without the need to have it passed as a prop.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createMergeableStore} from 'tinybase';
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
import {Provider, useSynchronizer} from 'tinybase/ui-react';
const App = ({synchronizer}) => (
<Provider synchronizer={synchronizer}>
<Pane />
</Provider>
);
const Pane = () => <span>{useSynchronizer().getStatus()}</span>;
const synchronizer = createLocalSynchronizer(createMergeableStore());
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App synchronizer={synchronizer} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
This example creates a Provider context into which a Synchronizer object is provided, named by Id. A component within it then uses the useSynchronizer hook with that Id to get a reference to the Synchronizer object again, without the need to have it passed as a prop.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createMergeableStore} from 'tinybase';
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
import {Provider, useSynchronizer} from 'tinybase/ui-react';
const App = ({synchronizer}) => (
<Provider synchronizersById={{petSynchronizer: synchronizer}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useSynchronizer('petSynchronizer').getStatus()}</span>
);
const synchronizer = createLocalSynchronizer(createMergeableStore());
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App synchronizer={synchronizer} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
Since
v5.3.0
useSynchronizerIds
The useSynchronizerIds hook is used to retrieve the Ids of all the named Synchronizer objects present in the current Provider component context.
useSynchronizerIds(): IdsExample
This example adds two named Synchronizer objects to a Provider context and an inner component accesses their Ids.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createMergeableStore} from 'tinybase';
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
import {
Provider,
useCreateMergeableStore,
useCreateSynchronizer,
useSynchronizerIds,
} from 'tinybase/ui-react';
const App = () => {
const store1 = useCreateMergeableStore(createMergeableStore);
const synchronizer1 = useCreateSynchronizer(
store1,
createLocalSynchronizer,
);
const store2 = useCreateMergeableStore(createMergeableStore);
const synchronizer2 = useCreateSynchronizer(
store2,
createLocalSynchronizer,
);
return (
<Provider synchronizersById={{synchronizer1, synchronizer2}}>
<Pane />
</Provider>
);
};
const Pane = () => <span>{JSON.stringify(useSynchronizerIds())}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
// ...
console.log(app.innerHTML);
// -> '<span>["synchronizer1","synchronizer2"]</span>'
Since
v5.3.0
useSynchronizerOrSynchronizerById
The useSynchronizerOrSynchronizerById hook is used to get a reference to a Synchronizer object from within a Provider component context, or have it passed directly to this hook.
useSynchronizerOrSynchronizerById(synchronizerOrSynchronizerId?: SynchronizerOrSynchronizerId): Synchronizer | undefined| Type | Description | |
|---|---|---|
synchronizerOrSynchronizerId? | SynchronizerOrSynchronizerId | Either an |
| returns | Synchronizer | undefined | A reference to the |
This is mostly of use when you are developing a component that needs a Synchronizer object and which might have been passed in explicitly to the component or is to be picked up from the context by Id (a common pattern for Synchronizer-based components).
This is unlikely to be used often. For most situations, you will want to use the useSynchronizer hook.
Example
This example creates a Provider context into which a default Synchronizer object is provided. A component within it then uses the useSynchronizerOrSynchronizerById hook to get a reference to the Synchronizer object again, without the need to have it passed as a prop. Note however, that unlike the useSynchronizer hook example, this component would also work if you were to pass the Synchronizer object directly into it, making it more portable.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createMergeableStore} from 'tinybase';
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
import {
Provider,
useSynchronizerOrSynchronizerById,
} from 'tinybase/ui-react';
const App = ({synchronizer}) => (
<Provider synchronizer={synchronizer}>
<Pane />
</Provider>
);
const Pane = ({synchronizer}) => (
<span>
{useSynchronizerOrSynchronizerById(synchronizer).getStatus()}
</span>
);
const synchronizer = createLocalSynchronizer(createMergeableStore());
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App synchronizer={synchronizer} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
Since
v5.3.0
useSynchronizerStatus
The useSynchronizerStatus hook returns a the status of a Synchronizer, and registers a listener so that any changes to it will cause a re-render.
useSynchronizerStatus(synchronizerOrSynchronizerId?: SynchronizerOrSynchronizerId): Status| Type | Description | |
|---|---|---|
synchronizerOrSynchronizerId? | SynchronizerOrSynchronizerId | The |
| returns | Status | The status of the |
A Provider component is used to wrap part of an application in a context, and it can contain a default Synchronizer or a set of Synchronizer objects named by Id. The useSynchronizerStatus hook lets you indicate which Synchronizer to get data for: omit the optional parameter for the default context Synchronizer, provide an Id for a named context Synchronizer, or provide a Synchronizer explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Synchronizer status will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Synchronizer outside the application, which is used in the useSynchronizerStatus hook by reference. A change to the status of the Synchronizer re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createMergeableStore} from 'tinybase';
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
import {useSynchronizerStatus} from 'tinybase/ui-react';
const synchronizer = createLocalSynchronizer(createMergeableStore());
const App = () => <span>{useSynchronizerStatus(synchronizer)}</span>;
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
console.log(app.innerHTML);
// -> '<span>0</span>'
This example creates a Provider context into which a default Synchronizer is provided. A component within it then uses the useSynchronizerStatus hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createMergeableStore} from 'tinybase';
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
import {Provider, useSynchronizerStatus} from 'tinybase/ui-react';
const App = ({synchronizer}) => (
<Provider synchronizer={synchronizer}>
<Pane />
</Provider>
);
const Pane = () => <span>{useSynchronizerStatus()}</span>;
const synchronizer = createLocalSynchronizer(createMergeableStore());
const app = document.createElement('div');
createRoot(app).render(<App synchronizer={synchronizer} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
This example creates a Provider context into which a Synchronizer is provided, named by Id. A component within it then uses the useSynchronizerStatus hook.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createMergeableStore} from 'tinybase';
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
import {Provider, useSynchronizerStatus} from 'tinybase/ui-react';
const App = ({synchronizer}) => (
<Provider synchronizersById={{petSynchronizer: synchronizer}}>
<Pane />
</Provider>
);
const Pane = () => <span>{useSynchronizerStatus('petSynchronizer')}</span>;
const synchronizer = createLocalSynchronizer(createMergeableStore());
const app = document.createElement('div');
createRoot(app).render(<App synchronizer={synchronizer} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
Since
v5.3.0
useSynchronizerStatusListener
The useSynchronizerStatusListener hook registers a listener function with the Synchronizer that will be called when its status changes.
useSynchronizerStatusListener(
listener: StatusListener<MergeableStoreOnly>,
listenerDeps?: DependencyList,
synchronizerOrSynchronizerId?: SynchronizerOrSynchronizerId,
): void| Type | Description | |
|---|---|---|
listener | StatusListener<MergeableStoreOnly> | The function that will be called whenever the status of the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
synchronizerOrSynchronizerId? | SynchronizerOrSynchronizerId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useSynchronizerStatus hook).
Unlike the addStatusListener method, which returns a listener Id and requires you to remove it manually, the useSynchronizerStatusListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Synchronizer will be deleted.
Example
This example uses the useSynchronizerStatusListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Synchronizer.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createMergeableStore} from 'tinybase';
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
import {Provider, useSynchronizerStatusListener} from 'tinybase/ui-react';
const App = ({synchronizer}) => (
<Provider synchronizer={synchronizer}>
<Pane />
</Provider>
);
const Pane = () => {
useSynchronizerStatusListener((synchronizer, status) =>
console.log('Synchronizer status changed: ' + status),
);
return <span>App</span>;
};
const synchronizer = createLocalSynchronizer(createMergeableStore());
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App synchronizer={synchronizer} />);
synchronizer.load();
// -> 'Synchronizer status changed: 1'
// ...
// -> 'Synchronizer status changed: 0'
synchronizer.save();
// -> 'Synchronizer status changed: 2'
// ...
// -> 'Synchronizer status changed: 0'
Since
v5.3.0
Checkpoints components
BackwardCheckpointsView
The BackwardCheckpointsView component renders a list of previous checkpoints that the underlying Store can go back to.
BackwardCheckpointsView(props: BackwardCheckpointsProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | BackwardCheckpointsProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the previous checkpoints, if present. |
The component's props identify which previous checkpoints to render based on the Checkpoints object (which is either the default context Checkpoints object, a named context Checkpoints object, or an explicit reference).
This component renders a list by iterating over each checkpoints. By default these are in turn rendered with the CheckpointView component, but you can override this behavior by providing a checkpointComponent prop, a custom component of your own that will render a checkpoint based on CheckpointProps. You can also pass additional props to your custom component with the getCheckpointComponentProps callback prop.
This component uses the useCheckpointIds hook under the covers, which means that any changes to the checkpoint Ids in the Checkpoints object will cause a re-render.
Examples
This example creates a Checkpoints object outside the application, which is used in the BackwardCheckpointsView component by reference to render a list of previous checkpoints.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createCheckpoints, createStore} from 'tinybase';
import {BackwardCheckpointsView} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {fido: {color: 'brown'}});
const checkpoints = createCheckpoints(store);
const App = () => (
<div>
<BackwardCheckpointsView checkpoints={checkpoints} separator="/" />
</div>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div></div>'
checkpoints.setCheckpoint('0', 'initial');
store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint('identified');
console.log(app.innerHTML);
// -> '<div>initial</div>'
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(app.innerHTML);
// -> '<div>initial/identified</div>'
This example creates a Provider context into which a default Checkpoints object is provided. The BackwardCheckpointsView component within it then renders the list of previous checkpoints (with Ids for readability).
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createCheckpoints, createStore} from 'tinybase';
import {BackwardCheckpointsView, Provider} from 'tinybase/ui-react';
const App = ({checkpoints}) => (
<Provider checkpoints={checkpoints}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<BackwardCheckpointsView debugIds={true} />
</div>
);
const store = createStore().setTable('pets', {fido: {color: 'brown'}});
const checkpoints = createCheckpoints(store);
checkpoints.setCheckpoint('0', 'initial');
store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint('identified');
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(app.innerHTML);
// -> '<div>0:{initial}1:{identified}</div>'
This example creates a Provider context into which a default Checkpoints object is provided. The BackwardCheckpointsView component within it then renders the list of previous checkpoints with a custom Row component and a custom props callback.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createCheckpoints, createStore} from 'tinybase';
import {
BackwardCheckpointsView,
CheckpointView,
Provider,
} from 'tinybase/ui-react';
const App = ({checkpoints}) => (
<Provider checkpoints={checkpoints}>
<Pane />
</Provider>
);
const getBoldProp = (checkpointId) => ({bold: checkpointId == '0'});
const Pane = () => (
<div>
<BackwardCheckpointsView
checkpointComponent={FormattedCheckpointView}
getCheckpointComponentProps={getBoldProp}
/>
</div>
);
const FormattedCheckpointView = ({checkpoints, checkpointId, bold}) => (
<span>
{bold ? <b>{checkpointId}</b> : checkpointId}
{': '}
<CheckpointView
checkpoints={checkpoints}
checkpointId={checkpointId}
/>
</span>
);
const store = createStore().setTable('pets', {fido: {color: 'brown'}});
const checkpoints = createCheckpoints(store);
checkpoints.setCheckpoint('0', 'initial');
store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint('identified');
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(app.innerHTML);
// -> '<div><span><b>0</b>: initial</span><span>1: identified</span></div>'
Since
v1.0.0
CheckpointView
The CheckpointView component simply renders the label of a checkpoint.
CheckpointView(props: CheckpointProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | CheckpointProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the checkpoint: its label if present, or |
The component's props identify which checkpoint to render based on Checkpoint Id and Checkpoints object (which is either the default context Checkpoints object, a named context Checkpoints object, or an explicit reference).
The primary purpose of this component is to render multiple checkpoints in a BackwardCheckpointsView component or ForwardCheckpointsView component.
This component uses the useCheckpoint hook under the covers, which means that any changes to the local Row Ids in the Relationship will cause a re-render.
Example
This example creates a Checkpoints object outside the application, which is used in the CheckpointView component by reference to render a checkpoint with a label (with its Id for readability).
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createCheckpoints, createStore} from 'tinybase';
import {CheckpointView} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {fido: {species: 'dog'}});
const checkpoints = createCheckpoints(store);
const App = () => (
<div>
<CheckpointView
checkpointId="1"
checkpoints={checkpoints}
debugIds={true}
/>
</div>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>1:{}</div>'
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(app.innerHTML);
// -> '<div>1:{sale}</div>'
checkpoints.setCheckpoint('1', 'sold');
console.log(app.innerHTML);
// -> '<div>1:{sold}</div>'
Since
v1.0.0
CurrentCheckpointView
The CurrentCheckpointView component renders the current checkpoint that the underlying Store is currently on.
CurrentCheckpointView(props: CurrentCheckpointProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | CurrentCheckpointProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the current checkpoint, if present. |
The component's props identify which current checkpoint to render based on the Checkpoints object (which is either the default context Checkpoints object, a named context Checkpoints object, or an explicit reference).
By default the current checkpoint is rendered with the CheckpointView component, but you can override this behavior by providing a checkpointComponent prop, a custom component of your own that will render a checkpoint based on CheckpointProps. You can also pass additional props to your custom component with the getCheckpointComponentProps callback prop.
This component uses the useCheckpointIds hook under the covers, which means that any changes to the current checkpoint Id in the Checkpoints object will cause a re-render.
Examples
This example creates a Checkpoints object outside the application, which is used in the CurrentCheckpointView component by reference to render the current checkpoints.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createCheckpoints, createStore} from 'tinybase';
import {CurrentCheckpointView} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {fido: {color: 'brown'}});
const checkpoints = createCheckpoints(store);
const App = () => (
<div>
<CurrentCheckpointView checkpoints={checkpoints} />
</div>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div></div>'
store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint('identified');
console.log(app.innerHTML);
// -> '<div>identified</div>'
store.setCell('pets', 'fido', 'sold', true);
console.log(app.innerHTML);
// -> '<div></div>'
checkpoints.addCheckpoint('sale');
console.log(app.innerHTML);
// -> '<div>sale</div>'
This example creates a Provider context into which a default Checkpoints object is provided. The CurrentCheckpointView component within it then renders current checkpoint (with its Id for readability).
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createCheckpoints, createStore} from 'tinybase';
import {CurrentCheckpointView, Provider} from 'tinybase/ui-react';
const App = ({checkpoints}) => (
<Provider checkpoints={checkpoints}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<CurrentCheckpointView debugIds={true} />
</div>
);
const store = createStore().setTable('pets', {fido: {color: 'brown'}});
const checkpoints = createCheckpoints(store);
store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint('identified');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(app.innerHTML);
// -> '<div>1:{identified}</div>'
This example creates a Provider context into which a default Checkpoints object is provided. The CurrentCheckpointView component within it then renders the list of future checkpoints with a custom Row component and a custom props callback.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createCheckpoints, createStore} from 'tinybase';
import {
CheckpointView,
CurrentCheckpointView,
Provider,
} from 'tinybase/ui-react';
const App = ({checkpoints}) => (
<Provider checkpoints={checkpoints}>
<Pane />
</Provider>
);
const getBoldProp = (checkpointId) => ({bold: checkpointId == '1'});
const Pane = () => (
<div>
<CurrentCheckpointView
checkpointComponent={FormattedCheckpointView}
getCheckpointComponentProps={getBoldProp}
/>
</div>
);
const FormattedCheckpointView = ({checkpoints, checkpointId, bold}) => (
<span>
{bold ? <b>{checkpointId}</b> : checkpointId}
{': '}
<CheckpointView
checkpoints={checkpoints}
checkpointId={checkpointId}
/>
</span>
);
const store = createStore().setTable('pets', {fido: {color: 'brown'}});
const checkpoints = createCheckpoints(store);
store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint('identified');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(app.innerHTML);
// -> '<div><span><b>1</b>: identified</span></div>'
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(app.innerHTML);
// -> '<div><span>2: sale</span></div>'
Since
v1.0.0
ForwardCheckpointsView
The ForwardCheckpointsView component renders a list of future checkpoints that the underlying Store can go forwards to.
ForwardCheckpointsView(props: ForwardCheckpointsProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | ForwardCheckpointsProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the future checkpoints, if present. |
The component's props identify which future checkpoints to render based on the Checkpoints object (which is either the default context Checkpoints object, a named context Checkpoints object, or an explicit reference).
This component renders a list by iterating over each checkpoints. By default these are in turn rendered with the CheckpointView component, but you can override this behavior by providing a checkpointComponent prop, a custom component of your own that will render a checkpoint based on CheckpointProps. You can also pass additional props to your custom component with the getCheckpointComponentProps callback prop.
This component uses the useCheckpointIds hook under the covers, which means that any changes to the checkpoint Ids in the Checkpoints object will cause a re-render.
Examples
This example creates a Checkpoints object outside the application, which is used in the ForwardCheckpointsView component by reference to render a list of future checkpoints.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createCheckpoints, createStore} from 'tinybase';
import {ForwardCheckpointsView} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {fido: {color: 'brown'}});
const checkpoints = createCheckpoints(store);
const App = () => (
<div>
<ForwardCheckpointsView checkpoints={checkpoints} separator="/" />
</div>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div></div>'
store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint('identified');
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
checkpoints.goBackward();
console.log(app.innerHTML);
// -> '<div>sale</div>'
checkpoints.goBackward();
console.log(app.innerHTML);
// -> '<div>identified/sale</div>'
This example creates a Provider context into which a default Checkpoints object is provided. The ForwardCheckpointsView component within it then renders the list of future checkpoints (with Ids for readability).
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createCheckpoints, createStore} from 'tinybase';
import {ForwardCheckpointsView, Provider} from 'tinybase/ui-react';
const App = ({checkpoints}) => (
<Provider checkpoints={checkpoints}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<ForwardCheckpointsView debugIds={true} />
</div>
);
const store = createStore().setTable('pets', {fido: {color: 'brown'}});
const checkpoints = createCheckpoints(store);
store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint('identified');
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
checkpoints.goTo('0');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(app.innerHTML);
// -> '<div>1:{identified}2:{sale}</div>'
This example creates a Provider context into which a default Checkpoints object is provided. The ForwardCheckpointsView component within it then renders the list of future checkpoints with a custom Row component and a custom props callback.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createCheckpoints, createStore} from 'tinybase';
import {
CheckpointView,
ForwardCheckpointsView,
Provider,
} from 'tinybase/ui-react';
const App = ({checkpoints}) => (
<Provider checkpoints={checkpoints}>
<Pane />
</Provider>
);
const getBoldProp = (checkpointId) => ({bold: checkpointId == '1'});
const Pane = () => (
<div>
<ForwardCheckpointsView
checkpointComponent={FormattedCheckpointView}
getCheckpointComponentProps={getBoldProp}
/>
</div>
);
const FormattedCheckpointView = ({checkpoints, checkpointId, bold}) => (
<span>
{bold ? <b>{checkpointId}</b> : checkpointId}
{': '}
<CheckpointView
checkpoints={checkpoints}
checkpointId={checkpointId}
/>
</span>
);
const store = createStore().setTable('pets', {fido: {color: 'brown'}});
const checkpoints = createCheckpoints(store);
store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint('identified');
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
checkpoints.goTo('0');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(app.innerHTML);
// -> '<div><span><b>1</b>: identified</span><span>2: sale</span></div>'
Since
v1.0.0
Context components
Provider
The Provider component is used to wrap part of an application in a context that provides default objects to be used by hooks and components within.
Provider(props: ProviderProps & {children: ReactNode}): ComponentReturnType| Type | Description | |
|---|---|---|
props | ProviderProps & {children: ReactNode} | The props for this component. |
| returns | ComponentReturnType | A rendering of the child components. |
Store, Metrics, Indexes, Relationships, Queries, Checkpoints, Persister, and Synchronizer objects can be passed into the context of an application and used throughout. One of each type of object can be provided as a default within the context. Additionally, multiple of each type of object can be provided in an Id-keyed map to the ___ById props.
Provider contexts can be nested and the objects passed in will be merged. For example, if an outer context contains a default Metrics object and an inner context contains only a default Store, both the Metrics objects and the Store will be visible within the inner context. If the outer context contains a Store named by Id and the inner context contains a Store named by a different Id, both will be visible within the inner context.
Examples
This example creates a Provider context into which a Store and a Metrics object are provided, one by default, and one named by Id. Components within it then render content from both, without the need to have them passed as props.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createMetrics, createStore} from 'tinybase';
import {CellView, Provider, useMetric} from 'tinybase/ui-react';
const App = ({store, metrics}) => (
<Provider store={store} metricsById={{petStore: metrics}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
<CellView tableId="species" rowId="dog" cellId="price" />,
<CellView tableId="species" rowId="cat" cellId="price" />,
{useMetric('highestPrice', 'petStore')}
</span>
);
const store = createStore();
store.setTable('species', {dog: {price: 5}, cat: {price: 4}});
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} metrics={metrics} />);
console.log(app.innerHTML);
// -> '<span>5,4,5</span>'
This example creates nested Provider contexts into which Store and Metrics objects are provided, showing how visibility is merged.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createMetrics, createStore} from 'tinybase';
import {
CellView,
Provider,
useCreateStore,
useMetric,
} from 'tinybase/ui-react';
const App = ({petStore, metrics}) => (
<Provider storesById={{pet: petStore}} metrics={metrics}>
<OuterPane />
</Provider>
);
const OuterPane = () => {
const planetStore = useCreateStore(() =>
createStore().setTables({planets: {mars: {moons: 2}}}),
);
return (
<Provider storesById={{planet: planetStore}}>
<InnerPane />
</Provider>
);
};
const InnerPane = () => (
<span>
<CellView tableId="species" rowId="dog" cellId="price" store="pet" />,
{useMetric('highestPrice')},
<CellView
tableId="planets"
rowId="mars"
cellId="moons"
store="planet"
/>
</span>
);
const petStore = createStore();
petStore.setTable('species', {dog: {price: 5}, cat: {price: 4}});
const metrics = createMetrics(petStore);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App petStore={petStore} metrics={metrics} />);
console.log(app.innerHTML);
// -> '<span>5,5,2</span>'
Since
v1.0.0
Indexes components
IndexView
The IndexView component renders the contents of a Index, and registers a listener so that any changes to that result will cause a re-render.
IndexView(props: IndexProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | IndexProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the |
The component's props identify which Index to render based on Index Id, and Indexes object (which is either the default context Indexes object, a named context Indexes object, or an explicit reference).
This component renders a Index by iterating over its Slice objects. By default these are in turn rendered with the SliceView component, but you can override this behavior by providing a sliceComponent prop, a custom component of your own that will render a Slice based on SliceProps. You can also pass additional props to your custom component with the getSliceComponentProps callback prop.
This component uses the useSliceIds hook under the covers, which means that any changes to the structure of the Index will cause a re-render.
Examples
This example creates an Indexes object outside the application, which is used in the IndexView component by reference. A change to the Slice Ids re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createIndexes, createStore} from 'tinybase';
import {IndexView} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const App = () => (
<div>
<IndexView indexId="bySpecies" indexes={indexes} separator="/" />
</div>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>dog/cat</div>'
store.setRow('pets', 'lowly', {species: 'worm'});
console.log(app.innerHTML);
// -> '<div>dog/cat/worm</div>'
This example creates a Provider context into which a default Indexes object is provided. The IndexView component within it then renders the Index (with Ids for readability).
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createIndexes, createStore} from 'tinybase';
import {IndexView, Provider} from 'tinybase/ui-react';
const App = ({indexes}) => (
<Provider indexes={indexes}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<IndexView indexId="bySpecies" debugIds={true} />
</div>
);
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const app = document.createElement('div');
createRoot(app).render(<App indexes={indexes} />);
console.log(app.innerHTML);
// -> '<div>bySpecies:{dog:{fido:{species:{dog}}cujo:{species:{dog}}}}</div>'
This example creates a Provider context into which a default Indexes object is provided. The IndexView component within it then renders the Index with a custom Slice component and a custom props callback.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createIndexes, createStore} from 'tinybase';
import {IndexView, Provider, SliceView} from 'tinybase/ui-react';
const App = ({indexes}) => (
<Provider indexes={indexes}>
<Pane />
</Provider>
);
const getBoldProp = (sliceId) => ({bold: sliceId == 'dog'});
const Pane = () => (
<div>
<IndexView
indexId="bySpecies"
sliceComponent={FormattedSliceView}
getSliceComponentProps={getBoldProp}
/>
</div>
);
const FormattedSliceView = ({indexId, sliceId, bold}) => (
<span>
{bold ? <b>{sliceId}</b> : sliceId}
{': '}
<SliceView indexId={indexId} sliceId={sliceId} separator="/" />
</span>
);
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const app = document.createElement('div');
createRoot(app).render(<App indexes={indexes} />);
console.log(app.innerHTML);
// -> '<div><span><b>dog</b>: dog/dog</span><span>cat: cat</span></div>'
Since
v1.0.0
SliceView
The SliceView component renders the contents of a Slice, and registers a listener so that any changes to that result will cause a re-render.
SliceView(props: SliceProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | SliceProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the |
The component's props identify which Slice to render based on Index Id, Slice Id, and Indexes object (which is either the default context Indexes object, a named context Indexes object, or an explicit reference).
This component renders a Slice by iterating over its Row objects. By default these are in turn rendered with the RowView component, but you can override this behavior by providing a rowComponent prop, a custom component of your own that will render a Row based on RowProps. You can also pass additional props to your custom component with the getRowComponentProps callback prop.
This component uses the useSliceRowIds hook under the covers, which means that any changes to the structure of the Slice will cause a re-render.
Examples
This example creates an Indexes object outside the application, which is used in the SliceView component by reference. A change to the Row Ids re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createIndexes, createStore} from 'tinybase';
import {SliceView} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const App = () => (
<div>
<SliceView
indexId="bySpecies"
sliceId="dog"
indexes={indexes}
separator="/"
/>
</div>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>dog</div>'
store.setRow('pets', 'cujo', {species: 'dog'});
console.log(app.innerHTML);
// -> '<div>dog/dog</div>'
This example creates a Provider context into which a default Indexes object is provided. The SliceView component within it then renders the Slice (with Ids for readability).
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createIndexes, createStore} from 'tinybase';
import {Provider, SliceView} from 'tinybase/ui-react';
const App = ({indexes}) => (
<Provider indexes={indexes}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<SliceView indexId="bySpecies" sliceId="dog" debugIds={true} />
</div>
);
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const app = document.createElement('div');
createRoot(app).render(<App indexes={indexes} />);
console.log(app.innerHTML);
// -> '<div>dog:{fido:{species:{dog}}cujo:{species:{dog}}}</div>'
This example creates a Provider context into which a default Indexes object is provided. The SliceView component within it then renders the Slice with a custom Row component and a custom props callback.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createIndexes, createStore} from 'tinybase';
import {Provider, RowView, SliceView} from 'tinybase/ui-react';
const App = ({indexes}) => (
<Provider indexes={indexes}>
<Pane />
</Provider>
);
const getBoldProp = (rowId) => ({bold: rowId == 'fido'});
const Pane = () => (
<div>
<SliceView
indexId="bySpecies"
sliceId="dog"
rowComponent={FormattedRowView}
getRowComponentProps={getBoldProp}
/>
</div>
);
const FormattedRowView = ({store, tableId, rowId, bold}) => (
<span>
{bold ? <b>{rowId}</b> : rowId}
{': '}
<RowView store={store} tableId={tableId} rowId={rowId} separator="/" />
</span>
);
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const app = document.createElement('div');
createRoot(app).render(<App indexes={indexes} />);
console.log(app.innerHTML);
// -> '<div><span><b>fido</b>: dog/brown</span><span>cujo: dog</span></div>'
Since
v1.0.0
Metrics components
MetricView
The MetricView component renders the current value of a Metric, and registers a listener so that any changes to that result will cause a re-render.
MetricView(props: MetricProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | MetricProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the |
The component's props can identify which Metrics object to get data for: omit the optional final parameter for the default context Metrics object, provide an Id for a named context Metrics object, or by explicit reference.
This component uses the useMetric hook under the covers, which means that any changes to the Metric will cause a re-render.
Examples
This example creates a Metrics object outside the application, which is used in the MetricView component hook by reference. A change to the Metric re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createMetrics, createStore} from 'tinybase';
import {MetricView} from 'tinybase/ui-react';
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
const App = () => (
<div>
<MetricView metricId="highestPrice" metrics={metrics} />
</div>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>5</div>'
store.setCell('species', 'horse', 'price', 20);
console.log(app.innerHTML);
// -> '<div>20</div>'
This example creates a Provider context into which a default Metrics object is provided. The MetricView component within it then renders the Metric (with its Id for readability).
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createMetrics, createStore} from 'tinybase';
import {MetricView, Provider} from 'tinybase/ui-react';
const App = ({metrics}) => (
<Provider metrics={metrics}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<MetricView metricId="highestPrice" debugIds={true} />
</div>
);
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
const app = document.createElement('div');
createRoot(app).render(<App metrics={metrics} />);
console.log(app.innerHTML);
// -> '<div>highestPrice:{5}</div>'
This example creates a Provider context into which a default Metrics object is provided. The MetricView component within it then attempts to render a non-existent Metric.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createMetrics, createStore} from 'tinybase';
import {MetricView, Provider} from 'tinybase/ui-react';
const App = ({metrics}) => (
<Provider metrics={metrics}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<MetricView metricId="lowestPrice" />
</div>
);
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
const app = document.createElement('div');
createRoot(app).render(<App metrics={metrics} />);
console.log(app.innerHTML);
// -> '<div></div>'
Since
v1.0.0
Queries components
ResultSortedTableView
The ResultSortedTableView component renders the contents of a single query's sorted ResultTable in a Queries object, and registers a listener so that any changes to that result will cause a re-render.
ResultSortedTableView(props: ResultSortedTableProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | ResultSortedTableProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the |
The component's props identify which ResultTable to render based on query Id, and Queries object (which is either the default context Queries object, a named context Queries object, or by explicit reference). It also takes a Cell Id to sort by and a boolean to indicate that the sorting should be in descending order. The offset and limit props are used to paginate results, but default to 0 and undefined to return all available Row Ids if not specified.
This component renders a ResultTable by iterating over its Row objects, in the order dictated by the sort parameters. By default these are in turn rendered with the ResultRowView component, but you can override this behavior by providing a resultRowComponent prop, a custom component of your own that will render a Row based on ResultRowProps. You can also pass additional props to your custom component with the getResultRowComponentProps callback prop.
This component uses the useResultSortedRowIds hook under the covers, which means that any changes to the structure or sorting of the ResultTable will cause a re-render.
Examples
This example creates a Queries object outside the application, which is used in the ResultSortedTableView component by reference. A change to the data in the Store re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {ResultSortedTableView} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => select('color'),
);
const App = () => (
<div>
<ResultSortedTableView
queryId="petColors"
cellId="color"
queries={queries}
separator="/"
/>
</div>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>black/brown</div>'
store.setCell('pets', 'felix', 'color', 'white');
console.log(app.innerHTML);
// -> '<div>brown/white</div>'
This example creates a Provider context into which a default Queries object is provided. The ResultSortedTableView component within it then renders the Table (with Ids for readability).
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {Provider, ResultSortedTableView} from 'tinybase/ui-react';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<ResultSortedTableView
queryId="petColors"
cellId="color"
debugIds={true}
/>
</div>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => select('color'));
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<div>petColors:{felix:{color:{black}}fido:{color:{brown}}}</div>'
This example creates a Provider context into which a default Queries object is provided. The ResultSortedTableView component within it then renders the Table with a custom Row component and a custom props callback.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {
Provider,
ResultRowView,
ResultSortedTableView,
} from 'tinybase/ui-react';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const getBoldProp = (rowId) => ({bold: rowId == 'fido'});
const Pane = () => (
<div>
<ResultSortedTableView
queryId="petColors"
cellId="color"
resultRowComponent={FormattedRowView}
getResultRowComponentProps={getBoldProp}
/>
</div>
);
const FormattedRowView = ({queryId, rowId, bold}) => (
<span>
{bold ? <b>{rowId}</b> : rowId}
{': '}
<ResultRowView queryId={queryId} rowId={rowId} />
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => select('color'));
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<div><span>felix: black</span><span><b>fido</b>: brown</span></div>'
Since
v2.0.0
ResultTableView
The ResultTableView component renders the contents of a single query's ResultTable in a Queries object, and registers a listener so that any changes to that result will cause a re-render.
ResultTableView(props: ResultTableProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | ResultTableProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the |
The component's props identify which ResultTable to render based on query Id, and Queries object (which is either the default context Queries object, a named context Queries object, or by explicit reference).
This component renders a ResultTable by iterating over its Row objects. By default these are in turn rendered with the ResultRowView component, but you can override this behavior by providing a resultRowComponent prop, a custom component of your own that will render a Row based on ResultRowProps. You can also pass additional props to your custom component with the getResultRowComponentProps callback prop.
This component uses the useResultRowIds hook under the covers, which means that any changes to the structure of the ResultTable will cause a re-render.
Examples
This example creates a Queries object outside the application, which is used in the ResultTableView component by reference. A change to the data in the Store re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {ResultTableView} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => select('color'),
);
const App = () => (
<div>
<ResultTableView queryId="petColors" queries={queries} separator="/" />
</div>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>brown/black</div>'
store.setRow('pets', 'cujo', {species: 'dog', color: 'black'});
console.log(app.innerHTML);
// -> '<div>brown/black/black</div>'
This example creates a Provider context into which a default Queries object is provided. The ResultTableView component within it then renders the Table (with Ids for readability).
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {Provider, ResultTableView} from 'tinybase/ui-react';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<ResultTableView queryId="petColors" debugIds={true} />
</div>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => select('color'));
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<div>petColors:{fido:{color:{brown}}felix:{color:{black}}}</div>'
This example creates a Provider context into which a default Queries object is provided. The ResultTableView component within it then renders the Table with a custom Row component and a custom props callback.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {Provider, ResultRowView, ResultTableView} from 'tinybase/ui-react';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const getBoldProp = (rowId) => ({bold: rowId == 'fido'});
const Pane = () => (
<div>
<ResultTableView
queryId="petColors"
resultRowComponent={FormattedRowView}
getResultRowComponentProps={getBoldProp}
/>
</div>
);
const FormattedRowView = ({queryId, rowId, bold}) => (
<span>
{bold ? <b>{rowId}</b> : rowId}
{': '}
<ResultRowView queryId={queryId} rowId={rowId} />
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => select('color'));
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<div><span><b>fido</b>: brown</span><span>felix: black</span></div>'
Since
v2.0.0
ResultRowView
The ResultRowView component renders the contents of a single Row in a given query's ResultTable, and registers a listener so that any changes to that result will cause a re-render.
ResultRowView(props: ResultRowProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | ResultRowProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the result |
The component's props identify which Row to render based on query Id, Row Id, and Queries object (which is either the default context Queries object, a named context Queries object, or an explicit reference).
This component renders a Row by iterating over its Cell values. By default these are in turn rendered with the ResultCellView component, but you can override this behavior by providing a resultCellComponent prop, a custom component of your own that will render a Cell based on ResultCellProps. You can also pass additional props to your custom component with the getResultCellComponentProps callback prop.
You can create your own ResultRowView-like component to customize the way that a result Row is rendered: see the ResultTableView component for more details.
This component uses the useResultCellIds hook under the covers, which means that any changes to the structure of the result Row will cause a re-render.
Examples
This example creates a Queries object outside the application, which is used in the ResultRowView component by reference. A change to the data in the Store re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {ResultRowView} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => {
select('species');
select('color');
},
);
const App = () => (
<div>
<ResultRowView
queryId="petColors"
rowId="fido"
queries={queries}
separator="/"
/>
</div>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>dog/brown</div>'
store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<div>dog/walnut</div>'
This example creates a Provider context into which a default Queries object is provided. The ResultRowView component within it then renders the Row (with Ids for readability).
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {Provider, ResultRowView} from 'tinybase/ui-react';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<ResultRowView queryId="petColors" rowId="fido" debugIds={true} />
</div>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => {
select('species');
select('color');
});
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<div>fido:{species:{dog}color:{brown}}</div>'
This example creates a Provider context into which a default Queries object is provided. The ResultRowView component within it then renders the Row with a custom Cell component and a custom props callback.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {Provider, ResultCellView, ResultRowView} from 'tinybase/ui-react';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const getBoldProp = (cellId) => ({bold: cellId == 'species'});
const Pane = () => (
<div>
<ResultRowView
queryId="petColors"
rowId="fido"
resultCellComponent={FormattedResultCellView}
getResultCellComponentProps={getBoldProp}
/>
</div>
);
const FormattedResultCellView = ({queryId, rowId, cellId, bold}) => (
<span>
{bold ? <b>{cellId}</b> : cellId}
{': '}
<ResultCellView queryId={queryId} rowId={rowId} cellId={cellId} />
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => {
select('species');
select('color');
});
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<div><span><b>species</b>: dog</span><span>color: brown</span></div>'
Since
v2.0.0
ResultCellView
The ResultCellView component renders the value of a single Cell in a given Row, in a given query's ResultTable, and registers a listener so that any changes to that result will cause a re-render.
ResultCellView(props: ResultCellProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | ResultCellProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the result |
The component's props identify which Cell to render based on query Id, Row Id, Cell Id, and Queries object (which is either the default context Queries object, a named context Queries object, or an explicit reference).
A Cell contains a string, number, or boolean, so the value is rendered directly without further decoration. You can create your own ResultCellView-like component to customize the way that a Cell is rendered: see the ResultRowView component for more details.
This component uses the useResultCell hook under the covers, which means that any changes to the specified Cell will cause a re-render.
Examples
This example creates a Queries object outside the application, which is used in the ResultCellView component by reference. A change to the data in the Store re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {ResultCellView} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => select('color'),
);
const App = () => (
<span>
<ResultCellView
queryId="petColors"
rowId="fido"
cellId="color"
queries={queries}
/>
</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>brown</span>'
store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<span>walnut</span>'
This example creates a Provider context into which a default Queries object is provided. The ResultCellView component within it then renders the Cell (with its Id for readability).
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {Provider, ResultCellView} from 'tinybase/ui-react';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
<ResultCellView
queryId="petColors"
rowId="fido"
cellId="color"
debugIds={true}
/>
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => select('color'));
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>color:{brown}</span>'
This example creates a Provider context into which a default Queries object is provided. The ResultCellView component within it then attempts to render a non-existent Cell.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {Provider, ResultCellView} from 'tinybase/ui-react';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
<ResultCellView queryId="petColors" rowId="fido" cellId="height" />
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => select('color'));
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span></span>'
Since
v2.0.0
Relationships components
LinkedRowsView
The LinkedRowsView component renders the local Row objects for a given remote Row in a Relationship, and registers a listener so that any changes to that result will cause a re-render.
LinkedRowsView(props: LinkedRowsProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | LinkedRowsProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the local |
The component's props identify which local Rows to render based on Relationship Id, remote Row Id, and Relationships object (which is either the default context Relationships object, a named context Relationships object, or an explicit reference).
By default the local Rows are rendered with the RowView component, but you can override this behavior by providing a rowComponent prop, a custom component of your own that will render the Row based on RowProps. You can also pass additional props to your custom component with the getRowComponentProps callback prop.
This component uses the useLocalRowIds hook under the covers, which means that any changes to the local Row Ids in the Relationship will cause a re-render.
Examples
This example creates a Relationships object outside the application, which is used in the LinkedRowsView component by reference. A change to the Row Ids re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createRelationships, createStore} from 'tinybase';
import {LinkedRowsView} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {
fido: {next: 'felix'},
felix: {next: 'cujo'},
cujo: {species: 'dog'},
});
const relationships = createRelationships(store).setRelationshipDefinition(
'petSequence',
'pets',
'pets',
'next',
);
const App = () => (
<div>
<LinkedRowsView
relationshipId="petSequence"
firstRowId="fido"
relationships={relationships}
separator="/"
/>
</div>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>felix/cujo/dog</div>'
store.setRow('pets', 'toto', {species: 'dog'});
store.setRow('pets', 'cujo', {next: 'toto'});
console.log(app.innerHTML);
// -> '<div>felix/cujo/toto/dog</div>'
This example creates a Provider context into which a default Relationships object is provided. The LinkedRowsView component within it then renders the local Row objects (with Ids for readability).
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createRelationships, createStore} from 'tinybase';
import {LinkedRowsView, Provider} from 'tinybase/ui-react';
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<LinkedRowsView
relationshipId="petSequence"
firstRowId="fido"
debugIds={true}
/>
</div>
);
const relationships = createRelationships(
createStore().setTable('pets', {
fido: {next: 'felix'},
felix: {species: 'cat'},
}),
).setRelationshipDefinition('petSequence', 'pets', 'pets', 'next');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<div>fido:{fido:{next:{felix}}felix:{species:{cat}}}</div>'
This example creates a Provider context into which a default Relationships object is provided. The LinkedRowsView component within it then renders the local Row objects with a custom Row component and a custom props callback.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createRelationships, createStore} from 'tinybase';
import {LinkedRowsView, Provider, RowView} from 'tinybase/ui-react';
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const getBoldProp = (rowId) => ({bold: rowId == 'fido'});
const Pane = () => (
<div>
<LinkedRowsView
relationshipId="petSequence"
firstRowId="fido"
rowComponent={FormattedRowView}
getRowComponentProps={getBoldProp}
/>
</div>
);
const FormattedRowView = ({store, tableId, rowId, bold}) => (
<span>
{bold ? <b>{rowId}</b> : rowId}
{': '}
<RowView store={store} tableId={tableId} rowId={rowId} />
</span>
);
const relationships = createRelationships(
createStore().setTable('pets', {
fido: {next: 'felix'},
felix: {species: 'cat'},
}),
).setRelationshipDefinition('petSequence', 'pets', 'pets', 'next');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<div><span><b>fido</b>: felix</span><span>felix: cat</span></div>'
Since
v1.0.0
LocalRowsView
The LocalRowsView component renders the local Row objects for a given remote Row in a Relationship, and registers a listener so that any changes to that result will cause a re-render.
LocalRowsView(props: LocalRowsProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | LocalRowsProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the local |
The component's props identify which local Rows to render based on Relationship Id, remote Row Id, and Relationships object (which is either the default context Relationships object, a named context Relationships object, or an explicit reference).
By default the local Rows are rendered with the RowView component, but you can override this behavior by providing a rowComponent prop, a custom component of your own that will render the Row based on RowProps. You can also pass additional props to your custom component with the getRowComponentProps callback prop.
This component uses the useLocalRowIds hook under the covers, which means that any changes to the local Row Ids in the Relationship will cause a re-render.
Examples
This example creates a Relationships object outside the application, which is used in the LocalRowsView component by reference. A change to the Row Ids re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createRelationships, createStore} from 'tinybase';
import {LocalRowsView} from 'tinybase/ui-react';
const store = createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}});
const relationships = createRelationships(store).setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
const App = () => (
<div>
<LocalRowsView
relationshipId="petSpecies"
remoteRowId="dog"
relationships={relationships}
separator="/"
/>
</div>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>dog/dog</div>'
store.setRow('pets', 'toto', {species: 'dog'});
console.log(app.innerHTML);
// -> '<div>dog/dog/dog</div>'
This example creates a Provider context into which a default Relationships object is provided. The LocalRowsView component within it then renders the local Row objects (with Ids for readability).
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createRelationships, createStore} from 'tinybase';
import {LocalRowsView, Provider} from 'tinybase/ui-react';
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<LocalRowsView
relationshipId="petSpecies"
remoteRowId="dog"
debugIds={true}
/>
</div>
);
const relationships = createRelationships(
createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<div>dog:{fido:{species:{dog}}cujo:{species:{dog}}}</div>'
This example creates a Provider context into which a default Relationships object is provided. The LocalRowsView component within it then renders the local Row objects with a custom Row component and a custom props callback.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createRelationships, createStore} from 'tinybase';
import {LocalRowsView, Provider, RowView} from 'tinybase/ui-react';
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const getBoldProp = (rowId) => ({bold: rowId == 'fido'});
const Pane = () => (
<div>
<LocalRowsView
relationshipId="petSpecies"
remoteRowId="dog"
rowComponent={FormattedRowView}
getRowComponentProps={getBoldProp}
/>
</div>
);
const FormattedRowView = ({store, tableId, rowId, bold}) => (
<span>
{bold ? <b>{rowId}</b> : rowId}
{': '}
<RowView store={store} tableId={tableId} rowId={rowId} />
</span>
);
const relationships = createRelationships(
createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<div><span><b>fido</b>: dog</span><span>cujo: dog</span></div>'
Since
v1.0.0
RemoteRowView
The RemoteRowView component renders the remote Row Id for a given local Row in a Relationship, and registers a listener so that any changes to that result will cause a re-render.
RemoteRowView(props: RemoteRowProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | RemoteRowProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the remote |
The component's props identify which remote Row to render based on Relationship Id, local Row Id, and Relationships object (which is either the default context Relationships object, a named context Relationships object, or an explicit reference).
By default the remote Row is rendered with the RowView component, but you can override this behavior by providing a rowComponent prop, a custom component of your own that will render the Row based on RowProps. You can also pass additional props to your custom component with the getRowComponentProps callback prop.
This component uses the useRemoteRowId hook under the covers, which means that any changes to the remote Row Id in the Relationship will cause a re-render.
Examples
This example creates a Relationships object outside the application, which is used in the RemoteRowView component by reference. A change to the Row Ids re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createRelationships, createStore} from 'tinybase';
import {RemoteRowView} from 'tinybase/ui-react';
const store = createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}});
const relationships = createRelationships(store).setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
const App = () => (
<div>
<RemoteRowView
relationshipId="petSpecies"
localRowId="cujo"
relationships={relationships}
/>
</div>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>5</div>'
store.setCell('pets', 'cujo', 'species', 'wolf');
console.log(app.innerHTML);
// -> '<div>10</div>'
This example creates a Provider context into which a default Relationships object is provided. The RemoteRowView component within it then renders the remote Row (with Ids for readability).
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createRelationships, createStore} from 'tinybase';
import {Provider, RemoteRowView} from 'tinybase/ui-react';
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<RemoteRowView
relationshipId="petSpecies"
localRowId="cujo"
debugIds={true}
/>
</div>
);
const relationships = createRelationships(
createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<div>cujo:{dog:{price:{5}}}</div>'
This example creates a Provider context into which a default Relationships object is provided. The RemoteRowView component within it then renders the remote Row with a custom Row component and a custom props callback.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createRelationships, createStore} from 'tinybase';
import {Provider, RemoteRowView, RowView} from 'tinybase/ui-react';
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const getBoldProp = (rowId) => ({bold: rowId == 'dog'});
const Pane = () => (
<div>
<RemoteRowView
relationshipId="petSpecies"
localRowId="cujo"
rowComponent={FormattedRowView}
getRowComponentProps={getBoldProp}
/>
</div>
);
const FormattedRowView = ({store, tableId, rowId, bold}) => (
<span>
{bold ? <b>{rowId}</b> : rowId}
{': '}
<RowView store={store} tableId={tableId} rowId={rowId} />
</span>
);
const relationships = createRelationships(
createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<div><span><b>dog</b>: 5</span></div>'
Since
v1.0.0
Store components
TablesView
The TablesView component renders the tabular contents of a Store, and registers a listener so that any changes to that result will cause a re-render.
TablesView(props: TablesProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | TablesProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the |
The component's props can identify which Store to render - either the default context Store, a named context Store, or an explicit reference.
This component renders a Store by iterating over its Table objects. By default these are in turn rendered with the TableView component, but you can override this behavior by providing a tableComponent prop, a custom component of your own that will render a Table based on TableProps. You can also pass additional props to your custom component with the getTableComponentProps callback prop.
This component uses the useTableIds hook under the covers, which means that any changes to the structure of the Store will cause a re-render.
Examples
This example creates a Store outside the application, which is used in the TablesView component by reference. A change to the data in the Store re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {TablesView} from 'tinybase/ui-react';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const App = () => (
<div>
<TablesView store={store} separator="/" />
</div>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>dog</div>'
store.setTable('species', {dog: {price: 5}});
console.log(app.innerHTML);
// -> '<div>dog/5</div>'
This example creates a Provider context into which a default Store is provided. The TablesView component within it then renders the Store (with Ids for readability).
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, TablesView} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<TablesView debugIds={true} />
</div>
);
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<div>pets:{fido:{species:{dog}}}species:{dog:{price:{5}}}</div>'
This example creates a Provider context into which a default Store is provided. The TablesView component within it then renders the Store with a custom Table component and a custom props callback.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, TableView, TablesView} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const getBoldProp = (tableId) => ({bold: tableId == 'pets'});
const Pane = () => (
<div>
<TablesView
tableComponent={FormattedTableView}
getTableComponentProps={getBoldProp}
/>
</div>
);
const FormattedTableView = ({tableId, bold}) => (
<span>
{bold ? <b>{tableId}</b> : tableId}
{': '}
<TableView tableId={tableId} />
</span>
);
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<div><span><b>pets</b>: dog</span><span>species: 5</span></div>'
Since
v1.0.0
TableView
The TableView component renders the contents of a single Table in a Store, and registers a listener so that any changes to that result will cause a re-render.
TableView(props: TableProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | TableProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the |
The component's props identify which Table to render based on Table Id, and Store (which is either the default context Store, a named context Store, or by explicit reference).
This component renders a Table by iterating over its Row objects. By default these are in turn rendered with the RowView component, but you can override this behavior by providing a rowComponent prop, a custom component of your own that will render a Row based on RowProps. You can also pass additional props to your custom component with the getRowComponentProps callback prop.
You can create your own TableView-like component to customize the way that a Table is rendered: see the TablesView component for more details.
This component uses the useRowIds hook under the covers, which means that any changes to the structure of the Table will cause a re-render.
Since v4.1.0, you can use the customCellIds prop if you want to render a prescribed set of the Table's Cells in a given order for each Row.
Examples
This example creates a Store outside the application, which is used in the TableView component by reference. A change to the data in the Store re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {TableView} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {fido: {species: 'dog'}});
const App = () => (
<div>
<TableView tableId="pets" store={store} separator="/" />
</div>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>dog</div>'
store.setRow('pets', 'felix', {species: 'cat'});
console.log(app.innerHTML);
// -> '<div>dog/cat</div>'
This example creates a Provider context into which a default Store is provided. The TableView component within it then renders the Table for a custom set of Cell Ids (and rendered with Ids for readability).
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, TableView} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const customCellIds = ['species'];
const Pane = () => (
<div>
<TableView
tableId="pets"
customCellIds={customCellIds}
debugIds={true}
/>
</div>
);
const store = createStore().setTable('pets', {
fido: {color: 'black', species: 'dog'},
felix: {color: 'brown', species: 'cat'},
});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<div>pets:{fido:{species:{dog}}felix:{species:{cat}}}</div>'
This example creates a Provider context into which a default Store is provided. The TableView component within it then renders the Table with a custom Row component and a custom props callback.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, RowView, TableView} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const getBoldProp = (rowId) => ({bold: rowId == 'fido'});
const Pane = () => (
<div>
<TableView
tableId="pets"
rowComponent={FormattedRowView}
getRowComponentProps={getBoldProp}
/>
</div>
);
const FormattedRowView = ({tableId, rowId, bold}) => (
<span>
{bold ? <b>{rowId}</b> : rowId}
{': '}
<RowView tableId={tableId} rowId={rowId} />
</span>
);
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<div><span><b>fido</b>: dog</span><span>felix: cat</span></div>'
Since
v1.0.0
SortedTableView
The SortedTableView component renders the contents of a single sorted Table in a Store, and registers a listener so that any changes to that result will cause a re-render.
SortedTableView(props: SortedTableProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | SortedTableProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the |
The component's props identify which Table to render based on Table Id, and Store (which is either the default context Store, a named context Store, or by explicit reference). It also takes a Cell Id to sort by and a boolean to indicate that the sorting should be in descending order. The offset and limit props are used to paginate results, but default to 0 and undefined to return all available Row Ids if not specified.
This component renders a Table by iterating over its Row objects, in the order dictated by the sort parameters. By default these are in turn rendered with the RowView component, but you can override this behavior by providing a rowComponent prop, a custom component of your own that will render a Row based on RowProps. You can also pass additional props to your custom component with the getRowComponentProps callback prop.
This component uses the useSortedRowIds hook under the covers, which means that any changes to the structure or sorting of the Table will cause a re-render.
Since v4.1.0, you can use the customCellIds prop if you want to render a prescribed set of the Table's Cells in a given order for each Row.
Examples
This example creates a Store outside the application, which is used in the SortedTableView component by reference. A change to the data in the Store re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {SortedTableView} from 'tinybase/ui-react';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
const App = () => (
<div>
<SortedTableView
tableId="pets"
cellId="species"
store={store}
separator="/"
/>
</div>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>cat/dog</div>'
store.setRow('pets', 'cujo', {species: 'wolf'});
console.log(app.innerHTML);
// -> '<div>cat/dog/wolf</div>'
This example creates a Provider context into which a default Store is provided. The SortedTableView component within it then renders the Table for a custom set of Cell Ids (and rendered with Ids for readability).
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, SortedTableView} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const customCellIds = ['species'];
const Pane = () => (
<div>
<SortedTableView
tableId="pets"
cellId="species"
customCellIds={customCellIds}
debugIds={true}
/>
</div>
);
const store = createStore().setTables({
pets: {
fido: {color: 'black', species: 'dog'},
felix: {color: 'brown', species: 'cat'},
},
});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<div>pets:{felix:{species:{cat}}fido:{species:{dog}}}</div>'
This example creates a Provider context into which a default Store is provided. The SortedTableView component within it then renders the Table with a custom Row component and a custom props callback.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, RowView, SortedTableView} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const getBoldProp = (rowId) => ({bold: rowId == 'fido'});
const Pane = () => (
<div>
<SortedTableView
tableId="pets"
cellId="species"
rowComponent={FormattedRowView}
getRowComponentProps={getBoldProp}
/>
</div>
);
const FormattedRowView = ({tableId, rowId, bold}) => (
<span>
{bold ? <b>{rowId}</b> : rowId}
{': '}
<RowView tableId={tableId} rowId={rowId} />
</span>
);
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<div><span>felix: cat</span><span><b>fido</b>: dog</span></div>'
Since
v2.0.0
RowView
The RowView component renders the contents of a single Row in a given Table, and registers a listener so that any changes to that result will cause a re-render.
RowView(props: RowProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | RowProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the |
The component's props identify which Row to render based on Table Id, Row Id, and Store (which is either the default context Store, a named context Store, or an explicit reference).
This component renders a Row by iterating over its Cell values. By default these are in turn rendered with the CellView component, but you can override this behavior by providing a cellComponent prop, a custom component of your own that will render a Cell based on CellProps. You can also pass additional props to your custom component with the getCellComponentProps callback prop.
You can create your own RowView-like component to customize the way that a Row is rendered: see the TableView component for more details.
Since v4.1.0, you can use the customCellIds prop if you want to render a prescribed set of the Row's Cells in a given order. Otherwise, this component uses the useCellIds hook under the covers, which means that any changes to the structure of the Row will cause a re-render.
Examples
This example creates a Store outside the application, which is used in the RowView component by reference. A change to the data in the Store re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {RowView} from 'tinybase/ui-react';
const store = createStore().setRow('pets', 'fido', {species: 'dog'});
const App = () => (
<div>
<RowView tableId="pets" rowId="fido" store={store} separator="/" />
</div>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>dog</div>'
store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<div>dog/walnut</div>'
This example creates a Provider context into which a default Store is provided. The RowView component within it then renders the Row for a custom set of Cell Ids (and rendered with Ids for readability).
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, RowView} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const customCellIds = ['color', 'species'];
const Pane = () => (
<div>
<RowView
tableId="pets"
rowId="fido"
customCellIds={customCellIds}
debugIds={true}
/>
</div>
);
const store = createStore().setRow('pets', 'fido', {
species: 'dog',
color: 'walnut',
legs: 4,
});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<div>fido:{color:{walnut}species:{dog}}</div>'
This example creates a Provider context into which a default Store is provided. The RowView component within it then renders the Row with a custom Cell component and a custom props callback.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {CellView, Provider, RowView} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const getBoldProp = (cellId) => ({bold: cellId == 'species'});
const Pane = () => (
<div>
<RowView
tableId="pets"
rowId="fido"
cellComponent={FormattedCellView}
getCellComponentProps={getBoldProp}
/>
</div>
);
const FormattedCellView = ({tableId, rowId, cellId, bold}) => (
<span>
{bold ? <b>{cellId}</b> : cellId}
{': '}
<CellView tableId={tableId} rowId={rowId} cellId={cellId} />
</span>
);
const store = createStore().setRow('pets', 'fido', {
species: 'dog',
color: 'walnut',
});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<div><span><b>species</b>: dog</span><span>color: walnut</span></div>'
Since
v1.0.0
CellView
The CellView component renders the value of a single Cell in a given Row, in a given Table, and registers a listener so that any changes to that result will cause a re-render.
CellView(props: CellProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | CellProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the |
The component's props identify which Cell to render based on Table Id, Row Id, Cell Id, and Store (which is either the default context Store, a named context Store, or an explicit reference).
A Cell contains a string, number, or boolean, so the value is rendered directly without further decoration. You can create your own CellView-like component to customize the way that a Cell is rendered: see the RowView component for more details.
This component uses the useCell hook under the covers, which means that any changes to the specified Cell will cause a re-render.
Examples
This example creates a Store outside the application, which is used in the CellView component by reference. A change to the data in the Store re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {CellView} from 'tinybase/ui-react';
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => (
<span>
<CellView tableId="pets" rowId="fido" cellId="color" store={store} />
</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>brown</span>'
store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<span>walnut</span>'
This example creates a Provider context into which a default Store is provided. The CellView component within it then renders the Cell (with its Id for readability).
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {CellView, Provider} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
<CellView tableId="pets" rowId="fido" cellId="color" debugIds={true} />
</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>color:{brown}</span>'
This example creates a Provider context into which a default Store is provided. The CellView component within it then attempts to render a non-existent Cell.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {CellView, Provider} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
<CellView tableId="pets" rowId="fido" cellId="height" />
</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span></span>'
Since
v1.0.0
ValuesView
The ValuesView component renders the keyed value contents of a Store, and registers a listener so that any changes to that result will cause a re-render.
ValuesView(props: ValuesProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | ValuesProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the |
The component's props can identify which Store to render - either the default context Store, a named context Store, or an explicit reference.
This component renders a Store by iterating over its Value objects. By default these are in turn rendered with the ValueView component, but you can override this behavior by providing a valueComponent prop, a custom component of your own that will render a Value based on ValueProps. You can also pass additional props to your custom component with the getValueComponentProps callback prop.
This component uses the useValueIds hook under the covers, which means that any changes to the Values in the Store will cause a re-render.
This component uses the useValueIds hook under the covers, which means that any changes to the Store's Values will cause a re-render.
Examples
This example creates a Store outside the application, which is used in the ValuesView component by reference. A change to the data in the Store re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {ValuesView} from 'tinybase/ui-react';
const store = createStore().setValue('open', true);
const App = () => (
<div>
<ValuesView store={store} separator="/" />
</div>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>true</div>'
store.setValue('employees', 3);
console.log(app.innerHTML);
// -> '<div>true/3</div>'
This example creates a Provider context into which a default Store is provided. The ValuesView component within it then renders the Values (with Ids for readability).
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, ValuesView} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<ValuesView debugIds={true} />
</div>
);
const store = createStore().setValues({open: true, employees: 3});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<div>open:{true}employees:{3}</div>'
This example creates a Provider context into which a default Store is provided. The ValuesView component within it then renders the Values with a custom Value component and a custom props callback.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, ValueView, ValuesView} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const getBoldProp = (valueId) => ({bold: valueId == 'open'});
const Pane = () => (
<div>
<ValuesView
valueComponent={FormattedValueView}
getValueComponentProps={getBoldProp}
/>
</div>
);
const FormattedValueView = ({valueId, bold}) => (
<span>
{bold ? <b>{valueId}</b> : valueId}
{': '}
<ValueView valueId={valueId} />
</span>
);
const store = createStore().setValues({open: true, employees: 3});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<div><span><b>open</b>: true</span><span>employees: 3</span></div>'
Since
v3.0.0
ValueView
The ValueView component renders the value of a single Value, and registers a listener so that any changes to that result will cause a re-render.
ValueView(props: ValueProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | ValueProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the |
The component's props identify which Value to render based on Value Id and Store (which is either the default context Store, a named context Store, or an explicit reference).
A Value contains a string, number, or boolean, so the value is rendered directly without further decoration. You can create your own ValueView-like component to customize the way that a Value is rendered: see the ValuesView component for more details.
This component uses the useValue hook under the covers, which means that any changes to the specified Value will cause a re-render.
Examples
This example creates a Store outside the application, which is used in the ValueView component by reference. A change to the data in the Store re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {ValueView} from 'tinybase/ui-react';
const store = createStore().setValue('open', true);
const App = () => (
<span>
<ValueView valueId="open" store={store} />
</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>true</span>'
store.setValue('open', false);
console.log(app.innerHTML);
// -> '<span>false</span>'
This example creates a Provider context into which a default Store is provided. The ValueView component within it then renders the Value (with its Id for readability).
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, ValueView} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
<ValueView valueId="open" debugIds={true} />
</span>
);
const store = createStore().setValue('open', true);
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>open:{true}</span>'
This example creates a Provider context into which a default Store is provided. The ValueView component within it then attempts to render a non-existent Value.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, ValueView} from 'tinybase/ui-react';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
<ValueView valueId="website" />
</span>
);
const store = createStore().setValue('open', true);
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span></span>'
Since
v3.0.0
Type Aliases
Checkpoints type aliases
UndoOrRedoInformation
The UndoOrRedoInformation type is an array that you can fetch from a Checkpoints object to that indicates if and how you can move the state of the underlying Store forward or backward.
[boolean, Callback, Id | undefined, string]This type is useful if you are building undo or redo buttons. See the useUndoInformation hook and the useRedoInformation hook for more details and examples.
Since
v1.0.0
Component type aliases
ComponentReturnType
ComponentReturnType is a simple alias for what a React component can return: either a ReactElement, or null for an empty component.
ReactElement<any, any> | nullSince
v1.0.0
Identity type aliases
StoreOrStoreId
The StoreOrStoreId type is used when you need to refer to a Store in a React hook or component.
Store | IdIn some simple cases you will already have a direct reference to the Store.
This module also includes a Provider component that can be used to wrap multiple Store objects into a context that can be used throughout the app. In this case you will want to refer to a Store by its Id in that context.
Many hooks and components in this ui-react module take this type as a parameter or a prop, allowing you to pass in either the Store or its Id.
Since
v1.0.0
CheckpointsOrCheckpointsId
The CheckpointsOrCheckpointsId type is used when you need to refer to a Checkpoints object in a React hook or component.
Checkpoints | IdIn some simple cases you will already have a direct reference to the Checkpoints object.
This module also includes a Provider component that can be used to wrap multiple Checkpoints objects into a context that can be used throughout the app. In this case you will want to refer to a Checkpoints object by its Id in that context.
Many hooks and components in this ui-react module take this type as a parameter or a prop, allowing you to pass in either the Checkpoints object or its Id.
Since
v1.0.0
GetId
The GetId type describes a function which, when passed a parameter, will return an Id.
(
parameter: Parameter,
store: Store,
): IdThis type is used in hooks that create callbacks - like the useSetTableCallback hook or useSetRowCallback hook - so that the Id arguments of the object to set can also be dependent on the event or parameter provided (as well as the object itself being set).
Since
v1.0.0
IndexesOrIndexesId
The IndexesOrIndexesId type is used when you need to refer to an Indexes object in a React hook or component.
Indexes | IdIn some simple cases you will already have a direct reference to the Indexes object.
This module also includes a Provider component that can be used to wrap multiple Indexes objects into a context that can be used throughout the app. In this case you will want to refer to an Indexes object by its Id in that context.
Many hooks and components in this ui-react module take this type as a parameter or a prop, allowing you to pass in either the Indexes object or its Id.
Since
v1.0.0
MetricsOrMetricsId
The MetricsOrMetricsId type is used when you need to refer to a Metrics object in a React hook or component.
Metrics | IdIn some simple cases you will already have a direct reference to the Metrics object.
This module also includes a Provider component that can be used to wrap multiple Metrics objects into a context that can be used throughout the app. In this case you will want to refer to a Metrics object by its Id in that context.
Many hooks and components in this ui-react module take this type as a parameter or a prop, allowing you to pass in either the Metrics object or its Id.
Since
v1.0.0
PersisterOrPersisterId
The PersisterOrPersisterId type is used when you need to refer to a Persister object in a React hook or component.
AnyPersister | IdIn some simple cases you will already have a direct reference to the Persister object.
This module also includes a Provider component that can be used to wrap multiple Persister objects into a context that can be used throughout the app. In this case you will want to refer to a Persister object by its Id in that context.
Many hooks and components in this ui-react module take this type as a parameter or a prop, allowing you to pass in either the Persister or its Id.
Since
v5.3.0
QueriesOrQueriesId
The QueriesOrQueriesId type is used when you need to refer to a Queries object in a React hook or component.
Queries | IdIn some simple cases you will already have a direct reference to the Queries object.
This module also includes a Provider component that can be used to wrap multiple Queries objects into a context that can be used throughout the app. In this case you will want to refer to a Queries object by its Id in that context.
Many hooks and components in this ui-react module take this type as a parameter or a prop, allowing you to pass in either the Queries object or its Id.
Since
v2.0.0
RelationshipsOrRelationshipsId
The RelationshipsOrRelationshipsId type is used when you need to refer to a Relationships object in a React hook or component.
Relationships | IdIn some simple cases you will already have a direct reference to the Relationships object.
This module also includes a Provider component that can be used to wrap multiple Relationships objects into a context that can be used throughout the app. In this case you will want to refer to a Relationships object by its Id in that context.
Many hooks and components in this ui-react module take this type as a parameter or a prop, allowing you to pass in either the Relationships object or its Id.
Since
v1.0.0
SynchronizerOrSynchronizerId
The SynchronizerOrSynchronizerId type is used when you need to refer to a Synchronizer object in a React hook or component.
Synchronizer | IdIn some simple cases you will already have a direct reference to the Synchronizer object.
This module also includes a Provider component that can be used to wrap multiple Synchronizer objects into a context that can be used throughout the app. In this case you will want to refer to a Synchronizer object by its Id in that context.
Many hooks and components in this ui-react module take this type as a parameter or a prop, allowing you to pass in either the Synchronizer or its Id.
Since
v5.3.0
Props type aliases
TablesProps
TablesProps props are used for components that refer to all the Tables in a Store, such as the TablesView component.
{
store?: StoreOrStoreId;
tableComponent?: ComponentType<TableProps>;
getTableComponentProps?: (tableId: Id) => ExtraProps;
separator?: ReactElement | string;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
store? | StoreOrStoreId | The |
tableComponent? | ComponentType<TableProps> | A component for rendering each |
getTableComponentProps? | (tableId: Id) => ExtraProps | A custom function for generating extra props for each |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
Since
v1.0.0
TableProps
TableProps props are used for components that refer to a single Table in a Store, such as the TableView component.
{
tableId: Id;
store?: StoreOrStoreId;
rowComponent?: ComponentType<RowProps>;
getRowComponentProps?: (rowId: Id) => ExtraProps;
customCellIds?: Ids;
separator?: ReactElement | string;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
tableId | Id | |
store? | StoreOrStoreId | The |
rowComponent? | ComponentType<RowProps> | A custom component for rendering each |
getRowComponentProps? | (rowId: Id) => ExtraProps | A function for generating extra props for each custom |
customCellIds? | Ids | An optional list of |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
Since
v1.0.0
SortedTableProps
SortedTableProps props are used for components that refer to a single sorted Table in a Store, such as the SortedTableView component.
{
tableId: Id;
cellId?: Id;
descending?: boolean;
offset?: number;
limit?: number;
store?: StoreOrStoreId;
rowComponent?: ComponentType<RowProps>;
getRowComponentProps?: (rowId: Id) => ExtraProps;
customCellIds?: Ids;
separator?: ReactElement | string;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
tableId | Id | |
cellId? | Id | The |
descending? | boolean | Whether the sorting should be in descending order. |
offset? | number | |
limit? | number | |
store? | StoreOrStoreId | The |
rowComponent? | ComponentType<RowProps> | A custom component for rendering each |
getRowComponentProps? | (rowId: Id) => ExtraProps | A function for generating extra props for each custom |
customCellIds? | Ids | An optional list of |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
Since
v2.0.0
RowProps
RowProps props are used for components that refer to a single Row in a Table, such as the RowView component.
{
tableId: Id;
rowId: Id;
store?: StoreOrStoreId;
cellComponent?: ComponentType<CellProps>;
getCellComponentProps?: (cellId: Id) => ExtraProps;
customCellIds?: Ids;
separator?: ReactElement | string;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
tableId | Id | |
rowId | Id | |
store? | StoreOrStoreId | The |
cellComponent? | ComponentType<CellProps> | A custom component for rendering each |
getCellComponentProps? | (cellId: Id) => ExtraProps | A function for generating extra props for each custom |
customCellIds? | Ids | An optional list of |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
Since
v1.0.0
CellProps
CellProps props are used for components that refer to a single Cell in a Row, such as the CellView component.
{
tableId: Id;
rowId: Id;
cellId: Id;
store?: StoreOrStoreId;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
tableId | Id | |
rowId | Id | |
cellId | Id | |
store? | StoreOrStoreId | The |
debugIds? | boolean | Whether the component should also render the |
Since
v1.0.0
MetricProps
MetricProps props are used for components that refer to a single Metric in a Metrics object, such as the MetricView component.
{
metricId: Id;
metrics?: MetricsOrMetricsId;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
metricId | Id | |
metrics? | MetricsOrMetricsId | The |
debugIds? | boolean | Whether the component should also render the |
Since
v1.0.0
IndexProps
IndexProps props are used for components that refer to a single Index in an Indexes object, such as the IndexView component.
{
indexId: Id;
indexes?: IndexesOrIndexesId;
sliceComponent?: ComponentType<SliceProps>;
getSliceComponentProps?: (sliceId: Id) => ExtraProps;
separator?: ReactElement | string;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
indexId | Id | |
indexes? | IndexesOrIndexesId | The |
sliceComponent? | ComponentType<SliceProps> | |
getSliceComponentProps? | (sliceId: Id) => ExtraProps | A function for generating extra props for each |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
Since
v1.0.0
SliceProps
SliceProps props are used for components that refer to a single Slice in an Index object, such as the SliceView component.
{
indexId: Id;
sliceId: Id;
indexes?: IndexesOrIndexesId;
rowComponent?: ComponentType<RowProps>;
getRowComponentProps?: (rowId: Id) => ExtraProps;
separator?: ReactElement | string;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
indexId | Id | |
sliceId | Id | |
indexes? | IndexesOrIndexesId | The |
rowComponent? | ComponentType<RowProps> | |
getRowComponentProps? | (rowId: Id) => ExtraProps | A function for generating extra props for each |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
Since
v1.0.0
LocalRowsProps
LocalRowsProps props are used for components that refer to a single Relationship in a Relationships object, and where you want to render local Rows based on a remote Row, such as the LocalRowsView component.
{
relationshipId: Id;
remoteRowId: Id;
relationships?: RelationshipsOrRelationshipsId;
rowComponent?: ComponentType<RowProps>;
getRowComponentProps?: (rowId: Id) => ExtraProps;
separator?: ReactElement | string;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
relationshipId | Id | The |
remoteRowId | Id | The |
relationships? | RelationshipsOrRelationshipsId | The |
rowComponent? | ComponentType<RowProps> | A component for rendering each (remote, local, or linked) |
getRowComponentProps? | (rowId: Id) => ExtraProps | A function for generating extra props for each |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
Since
v1.0.0
RemoteRowProps
RemoteRowProps props are used for components that refer to a single Relationship in a Relationships object, and where you want to render a remote Row based on a local Row, such as in the RemoteRowView component.
{
relationshipId: Id;
localRowId: Id;
relationships?: RelationshipsOrRelationshipsId;
rowComponent?: ComponentType<RowProps>;
getRowComponentProps?: (rowId: Id) => ExtraProps;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
relationshipId | Id | The |
localRowId | Id | |
relationships? | RelationshipsOrRelationshipsId | The |
rowComponent? | ComponentType<RowProps> | A component for rendering each (remote, local, or linked) |
getRowComponentProps? | (rowId: Id) => ExtraProps | A function for generating extra props for each |
debugIds? | boolean | Whether the component should also render the |
Since
v1.0.0
LinkedRowsProps
LinkedRowsProps props are used for components that refer to a single Relationship in a Relationships object, and where you want to render a linked list of Rows starting from a first Row, such as the LinkedRowsView component.
{
relationshipId: Id;
firstRowId: Id;
relationships?: RelationshipsOrRelationshipsId;
rowComponent?: ComponentType<RowProps>;
getRowComponentProps?: (rowId: Id) => ExtraProps;
separator?: ReactElement | string;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
relationshipId | Id | The |
firstRowId | Id | The |
relationships? | RelationshipsOrRelationshipsId | The |
rowComponent? | ComponentType<RowProps> | A component for rendering each (remote, local, or linked) |
getRowComponentProps? | (rowId: Id) => ExtraProps | A function for generating extra props for each |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
Since
v1.0.0
ResultTableProps
ResultTableProps props are used for components that refer to a single query ResultTable, such as the ResultTableView component.
{
queryId: Id;
queries?: QueriesOrQueriesId;
resultRowComponent?: ComponentType<ResultRowProps>;
getResultRowComponentProps?: (rowId: Id) => ExtraProps;
separator?: ReactElement | string;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
queryId | Id | The |
queries? | QueriesOrQueriesId | The |
resultRowComponent? | ComponentType<ResultRowProps> | A custom component for rendering each |
getResultRowComponentProps? | (rowId: Id) => ExtraProps | A function for generating extra props for each custom |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
Since
v2.0.0
ResultSortedTableProps
ResultSortedTableProps props are used for components that refer to a single sorted query ResultTable, such as the ResultSortedTableView component.
{
queryId: Id;
cellId?: Id;
descending?: boolean;
offset?: number;
limit?: number;
queries?: QueriesOrQueriesId;
resultRowComponent?: ComponentType<ResultRowProps>;
getResultRowComponentProps?: (rowId: Id) => ExtraProps;
separator?: ReactElement | string;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
queryId | Id | The |
cellId? | Id | The |
descending? | boolean | Whether the sorting should be in descending order. |
offset? | number | |
limit? | number | |
queries? | QueriesOrQueriesId | The |
resultRowComponent? | ComponentType<ResultRowProps> | A custom component for rendering each |
getResultRowComponentProps? | (rowId: Id) => ExtraProps | A function for generating extra props for each custom |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
Since
v2.0.0
ResultRowProps
ResultRowProps props are used for components that refer to a single Row in a query ResultTable, such as the ResultRowView component.
{
queryId: Id;
rowId: Id;
queries?: QueriesOrQueriesId;
resultCellComponent?: ComponentType<ResultCellProps>;
getResultCellComponentProps?: (cellId: Id) => ExtraProps;
separator?: ReactElement | string;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
queryId | Id | The |
rowId | Id | The |
queries? | QueriesOrQueriesId | The |
resultCellComponent? | ComponentType<ResultCellProps> | A custom component for rendering each |
getResultCellComponentProps? | (cellId: Id) => ExtraProps | A function for generating extra props for each custom |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
Since
v2.0.0
ResultCellProps
ResultRowProps props are used for components that refer to a single Cell in a Row of a ResultTable, such as the ResultCellView component.
{
queryId: Id;
rowId: Id;
cellId: Id;
queries?: QueriesOrQueriesId;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
queryId | Id | The |
rowId | Id | |
cellId | Id | |
queries? | QueriesOrQueriesId | The |
debugIds? | boolean | Whether the component should also render the |
Since
v2.0.0
BackwardCheckpointsProps
BackwardCheckpointsProps props are used for components that refer to a list of previous checkpoints in a Checkpoints object, such as the BackwardCheckpointsView component.
{
checkpoints?: CheckpointsOrCheckpointsId;
checkpointComponent?: ComponentType<CheckpointProps>;
getCheckpointComponentProps?: (checkpointId: Id) => ExtraProps;
separator?: ReactElement | string;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
checkpoints? | CheckpointsOrCheckpointsId | The |
checkpointComponent? | ComponentType<CheckpointProps> | A component for rendering each checkpoint in the |
getCheckpointComponentProps? | (checkpointId: Id) => ExtraProps | A function for generating extra props for each checkpoint component based on its |
separator? | ReactElement | string | A component or string to separate each Checkpoint component. |
debugIds? | boolean | Whether the component should also render the |
Since
v1.0.0
CurrentCheckpointProps
CurrentCheckpointsProps props are used for components that refer to the current checkpoints in a Checkpoints object, such as the BackwardCheckpointsView component.
{
checkpoints?: CheckpointsOrCheckpointsId;
checkpointComponent?: ComponentType<CheckpointProps>;
getCheckpointComponentProps?: (checkpointId: Id) => ExtraProps;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
checkpoints? | CheckpointsOrCheckpointsId | The |
checkpointComponent? | ComponentType<CheckpointProps> | A component for rendering each checkpoint in the |
getCheckpointComponentProps? | (checkpointId: Id) => ExtraProps | A function for generating extra props for each checkpoint component based on its |
debugIds? | boolean | Whether the component should also render the |
Since
v1.0.0
ForwardCheckpointsProps
ForwardCheckpointsProps props are used for components that refer to a list of future checkpoints in a Checkpoints object, such as the ForwardCheckpointsView component.
{
checkpoints?: CheckpointsOrCheckpointsId;
checkpointComponent?: ComponentType<CheckpointProps>;
getCheckpointComponentProps?: (checkpointId: Id) => ExtraProps;
separator?: ReactElement | string;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
checkpoints? | CheckpointsOrCheckpointsId | The |
checkpointComponent? | ComponentType<CheckpointProps> | A component for rendering each checkpoint in the |
getCheckpointComponentProps? | (checkpointId: Id) => ExtraProps | A function for generating extra props for each checkpoint component based on its |
separator? | ReactElement | string | A component or string to separate each Checkpoint component. |
debugIds? | boolean | Whether the component should also render the |
Since
v1.0.0
ProviderProps
ProviderProps props are used with the Provider component, so that Store Metrics, Indexes, Relationships, Queries, and Checkpoints objects can be passed into the context of an application and used throughout.
{
store?: Store;
storesById?: {[storeId: Id]: Store};
metrics?: Metrics;
metricsById?: {[metricsId: Id]: Metrics};
indexes?: Indexes;
indexesById?: {[indexesId: Id]: Indexes};
relationships?: Relationships;
relationshipsById?: {[relationshipsId: Id]: Relationships};
queries?: Queries;
queriesById?: {[queriesId: Id]: Queries};
checkpoints?: Checkpoints;
checkpointsById?: {[checkpointsId: Id]: Checkpoints};
persister?: AnyPersister;
persistersById?: {[persisterId: Id]: AnyPersister};
synchronizer?: Synchronizer;
synchronizersById?: {[synchronizerId: Id]: Synchronizer};
}| Type | Description | |
|---|---|---|
store? | Store | A default single |
storesById? | {[storeId: Id]: Store} | An object containing multiple |
metrics? | Metrics | A default single |
metricsById? | {[metricsId: Id]: Metrics} | An object containing multiple |
indexes? | Indexes | A default single |
indexesById? | {[indexesId: Id]: Indexes} | An object containing multiple |
relationships? | Relationships | A default single |
relationshipsById? | {[relationshipsId: Id]: Relationships} | An object containing multiple |
queries? | Queries | A default single |
queriesById? | {[queriesId: Id]: Queries} | An object containing multiple |
checkpoints? | Checkpoints | A default single |
checkpointsById? | {[checkpointsId: Id]: Checkpoints} | An object containing multiple |
persister? | AnyPersister | A default single |
persistersById? | {[persisterId: Id]: AnyPersister} | An object containing multiple |
synchronizer? | Synchronizer | A default single |
synchronizersById? | {[synchronizerId: Id]: Synchronizer} | An object containing multiple |
One of each type of object can be provided as a default within the context. Additionally, multiple of each type of object can be provided in an Id-keyed map to the ___ById props.
Since
v1.0.0
ExtraProps
The ExtraProps type represents a set of arbitrary additional props.
{[propName: string]: any}Since
v1.0.0
ValuesProps
ValuesProps props are used for components that refer to all the Values in a Store, such as the ValuesView component.
{
store?: StoreOrStoreId;
valueComponent?: ComponentType<ValueProps>;
getValueComponentProps?: (valueId: Id) => ExtraProps;
separator?: ReactElement | string;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
store? | StoreOrStoreId | The |
valueComponent? | ComponentType<ValueProps> | A custom component for rendering each |
getValueComponentProps? | (valueId: Id) => ExtraProps | A function for generating extra props for each custom |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
Since
v3.0.0
ValueProps
ValueProps props are used for components that refer to a single Value in a Row, such as the ValueView component.
{
valueId: Id;
store?: StoreOrStoreId;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
valueId | Id | |
store? | StoreOrStoreId | The |
debugIds? | boolean | Whether the component should also render the |
Since
v3.0.0
CheckpointProps
CheckpointProps props are used for components that refer to a single checkpoint in a Checkpoints object, such as the CheckpointView component.
{
checkpointId: Id;
checkpoints?: CheckpointsOrCheckpointsId;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
checkpointId | Id | The |
checkpoints? | CheckpointsOrCheckpointsId | The |
debugIds? | boolean | Whether the component should also render the |
Since
v1.0.0
ui-react-dom
The ui-react-dom module of the TinyBase project provides components to make it easy to create web-based reactive apps with Store objects.
The components in this module use the react-dom module and so are not appropriate for environments like React Native (although those in the lower-level ui-react module are).
See also
UI Components demos
Since
v4.1.0
Functions
Indexes components
SliceInHtmlTable
The SliceInHtmlTable component renders the contents of a Slice as an HTML
SliceInHtmlTable(props: SliceInHtmlTableProps & HtmlTableProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | SliceInHtmlTableProps & HtmlTableProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the |
See the <SliceInHtmlTable /> demo for this component in action.
The component's props identify which Slice to render based on Index Id, Slice Id, and Indexes object (which is either the default context Indexes object, a named context Indexes object, or an explicit reference).
This component renders a Slice by iterating over its Row objects. By default the Cells are in turn rendered with the CellView component, but you can override this behavior by providing a component for each Cell in the customCells prop. You can pass additional props to that custom component with the getComponentProps callback. See the CustomCell type for more details.
This component uses the useSliceRowIds hook under the covers, which means that any changes to the structure of the Slice will cause a re-render.
You can use the headerRow and idColumn props to control whether labels and Ids appear in a <th> element at the top of the table, and the start of each row.
Examples
This example creates a Provider context into which a default Indexes object is provided. The SliceInHtmlTable component within it then renders the Slice in a <table> element with a CSS class.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createIndexes, createStore} from 'tinybase';
import {Provider} from 'tinybase/ui-react';
import {SliceInHtmlTable} from 'tinybase/ui-react-dom';
const App = ({indexes}) => (
<Provider indexes={indexes}>
<Pane />
</Provider>
);
const Pane = () => (
<SliceInHtmlTable indexId="bySpecies" sliceId="dog" className="slice" />
);
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const app = document.createElement('div');
createRoot(app).render(<App indexes={indexes} />);
console.log(app.innerHTML);
// ->
`
<table class="slice">
<thead>
<tr>
<th>Id</th>
<th>species</th>
</tr>
</thead>
<tbody>
<tr>
<th title="fido">fido</th>
<td>dog</td>
</tr>
<tr>
<th title="cujo">cujo</th>
<td>dog</td>
</tr>
</tbody>
</table>
`;
This example creates a Provider context into which a default Indexes object is provided. The SliceInHtmlTable component within it then renders the Slice with a custom component and a custom props callback for the species Cell. The header row at the top of the table and the Id column at the start of each row is removed.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createIndexes, createStore} from 'tinybase';
import {CellView, Provider} from 'tinybase/ui-react';
import {SliceInHtmlTable} from 'tinybase/ui-react-dom';
const App = ({indexes}) => (
<Provider indexes={indexes}>
<Pane />
</Provider>
);
const Pane = () => (
<SliceInHtmlTable
indexId="bySpecies"
sliceId="dog"
customCells={customCells}
headerRow={false}
idColumn={false}
/>
);
const FormattedCellView = ({tableId, rowId, cellId, bold}) => (
<>
{bold ? <b>{rowId}</b> : rowId}:
<CellView tableId={tableId} rowId={rowId} cellId={cellId} />
</>
);
const customCells = {
species: {
component: FormattedCellView,
getComponentProps: (rowId) => ({bold: rowId == 'fido'}),
},
};
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const app = document.createElement('div');
createRoot(app).render(<App indexes={indexes} />);
console.log(app.innerHTML);
// ->
`
<table>
<tbody>
<tr>
<td><b>fido</b>:</td>
</tr>
<tr>
<td>cujo:</td>
</tr>
</tbody>
</table>
`;
Since
v4.1.0
Queries components
ResultSortedTableInHtmlTable
The SortedTableInHtmlTable component renders the contents of a single query's sorted ResultTable in a Queries object as an HTML <table> element, and registers a listener so that any changes to that result will cause a re-render.
ResultSortedTableInHtmlTable(props: ResultSortedTableInHtmlTableProps & HtmlTableProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | ResultSortedTableInHtmlTableProps & HtmlTableProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the |
See the <ResultSortedTableInHtmlTable /> demo for this component in action.
The component's props identify which ResultTable to render based on query Id, and Queries object (which is either the default context Queries object, a named context Queries object, or by explicit reference). It also takes a Cell Id to sort by and a boolean to indicate that the sorting should be in descending order. The offset and limit props are used to paginate results, but default to 0 and undefined to return all available Row Ids if not specified.
This component renders a ResultTable by iterating over its Row objects, in the order dictated by the sort parameters. By default the Cells are in turn rendered with the CellView component, but you can override this behavior by providing a component for each Cell in the customCells prop. You can pass additional props to that custom component with the getComponentProps callback. See the ResultCustomCell type for more details.
This component uses the useSortedRowIds hook under the covers, which means that any changes to the structure or sorting of the ResultTable will cause a re-render.
You can use the headerRow and idColumn props to control whether the Ids appear in a <th> element at the top of the table, and the start of each row.
The sortOnClick prop makes the table's sorting interactive such that the user can click on a column heading to sort by that column. The style classes sorted and ascending (or descending) are added so that you can provide hints to the user how the sorting is being applied.
Provide a paginator component for the ResultTable with the paginator prop. Set to true to use the default SortedTablePaginator, or provide your own component that accepts SortedTablePaginatorProps.
Finally, the onChange prop lets you listen to a user's changes to the ResultTable's sorting or pagination.
Examples
This example creates a Provider context into which a default Queries object is provided. The ResultSortedTableInHtmlTable component within it then renders the ResultTable in a <table> element with a CSS class.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {Provider} from 'tinybase/ui-react';
import {ResultSortedTableInHtmlTable} from 'tinybase/ui-react-dom';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<ResultSortedTableInHtmlTable
queryId="petColors"
cellId="color"
className="table"
/>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => select('color'));
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// ->
`
<table class="table">
<thead>
<tr>
<th>Id</th>
<th class="sorted ascending">↑ color</th>
</tr>
</thead>
<tbody>
<tr>
<th title="felix">felix</th>
<td>black</td>
</tr>
<tr>
<th title="fido">fido</th>
<td>brown</td>
</tr>
</tbody>
</table>
`;
This example creates a Provider context into which a default Queries object is provided. The ResultSortedTableInHtmlTable component within it then renders the ResultTable with a custom component and a custom props callback for the color Cell. The header row at the top of the table and the Id column at the start of each row is removed.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {Provider, ResultCellView} from 'tinybase/ui-react';
import {ResultSortedTableInHtmlTable} from 'tinybase/ui-react-dom';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<ResultSortedTableInHtmlTable
queryId="petColors"
cellId="color"
customCells={customCells}
headerRow={false}
idColumn={false}
/>
);
const FormattedResultCellView = ({queryId, rowId, cellId, bold}) => (
<>
{bold ? <b>{rowId}</b> : rowId}:
<ResultCellView queryId={queryId} rowId={rowId} cellId={cellId} />
</>
);
const customCells = {
color: {
component: FormattedResultCellView,
getComponentProps: (rowId) => ({bold: rowId == 'fido'}),
},
};
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => select('color'));
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// ->
`
<table>
<tbody>
<tr>
<td>felix:black</td>
</tr>
<tr>
<td><b>fido</b>:brown</td>
</tr>
</tbody>
</table>
`;
Since
v4.1.0
ResultTableInHtmlTable
The ResultTableInHtmlTable component renders the contents of a single query's ResultTable in a Queries object as an HTML <table> element, and registers a listener so that any changes to that result will cause a re-render.
ResultTableInHtmlTable(props: ResultTableInHtmlTableProps & HtmlTableProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | ResultTableInHtmlTableProps & HtmlTableProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the |
See the <ResultTableInHtmlTable /> demo for this component in action.
The component's props identify which ResultTable to render based on query Id, and Queries object (which is either the default context Queries object, a named context Queries object, or by explicit reference).
This component renders a ResultTable by iterating over its Row objects. By default the Cells are in turn rendered with the CellView component, but you can override this behavior by providing a component for each Cell in the customCells prop. You can pass additional props to that custom component with the getComponentProps callback. See the ResultCustomCell type for more details.
This component uses the useRowIds hook under the covers, which means that any changes to the structure of the Table will cause a re-render.
You can use the headerRow and idColumn props to control whether the Ids appear in a <th> element at the top of the table, and the start of each row.
Examples
This example creates a Provider context into which a default Queries object is provided. The ResultTableInHtmlTable component within it then renders the ResultTable in a <table> element with a CSS class.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {Provider} from 'tinybase/ui-react';
import {ResultTableInHtmlTable} from 'tinybase/ui-react-dom';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<ResultTableInHtmlTable queryId="petColors" className="table" />
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => select('color'));
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// ->
`
<table class="table">
<thead>
<tr>
<th>Id</th>
<th>color</th>
</tr>
</thead>
<tbody>
<tr>
<th title="fido">fido</th>
<td>brown</td>
</tr>
<tr>
<th title="felix">felix</th>
<td>black</td>
</tr>
</tbody>
</table>
`;
This example creates a Provider context into which a default Queries object is provided. The ResultTableInHtmlTable component within it then renders the ResultTable with a custom component and a custom props callback for the color Cell. The header row at the top of the table and the Id column at the start of each row is removed.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createQueries, createStore} from 'tinybase';
import {Provider, ResultCellView} from 'tinybase/ui-react';
import {ResultTableInHtmlTable} from 'tinybase/ui-react-dom';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<ResultTableInHtmlTable
queryId="petColors"
customCells={customCells}
headerRow={false}
idColumn={false}
/>
);
const FormattedResultCellView = ({queryId, rowId, cellId, bold}) => (
<>
{bold ? <b>{rowId}</b> : rowId}:
<ResultCellView queryId={queryId} rowId={rowId} cellId={cellId} />
</>
);
const customCells = {
color: {
component: FormattedResultCellView,
getComponentProps: (rowId) => ({bold: rowId == 'fido'}),
},
};
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => select('color'));
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// ->
`
<table>
<tbody>
<tr>
<td><b>fido</b>:brown</td>
</tr>
<tr>
<td>felix:black</td>
</tr>
</tbody>
</table>
`;
Since
v4.1.0
Relationships components
RelationshipInHtmlTable
The RelationshipInHtmlTable component renders the contents of the two Tables linked by a Relationship as an HTML <table> element, and registers a listener so that any changes to that result will cause a re-render.
RelationshipInHtmlTable(props: RelationshipInHtmlTableProps & HtmlTableProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | RelationshipInHtmlTableProps & HtmlTableProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the two |
See the <RelationshipInHtmlTable /> demo for this component in action.
The component's props identify which Relationship to render based on Relationship Id and Relationships object (which is either the default context Relationships object, a named context Relationships object, or an explicit reference).
This component renders the two Table objects by iterating over their related Row objects. By default the Cells are in turn rendered with the CellView component, but you can override this behavior by providing a component for each Cell in the customCells prop. You can pass additional props to that custom component with the getComponentProps callback. See the CustomCell type for more details.
Note the use of dotted 'tableId.cellId' string pairs when specifying custom rendering for the cells in this table, since Cells from both the relationship's 'local' and 'remote' Table objects can be rendered and need to be distinguished.
This component uses the useRowIds and useRemoteRowId hooks under the covers, which means that any changes to the structure of either Table resulting in a change to the relationship will cause a re-render.
You can use the headerRow and idColumn props to control whether labels and Ids appear in a <th> element at the top of the table, and the start of each row.
Examples
This example creates a Provider context into which a default Relationships object is provided. The RelationshipInHtmlTable component within it then renders the two Tables linked by a relationship in a <table> element with a CSS class. Note the dotted pairs that are used as column headings.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createRelationships, createStore} from 'tinybase';
import {Provider} from 'tinybase/ui-react';
import {RelationshipInHtmlTable} from 'tinybase/ui-react-dom';
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = () => (
<RelationshipInHtmlTable
relationshipId="petSpecies"
className="relationship"
/>
);
const relationships = createRelationships(
createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// ->
`
<table class="relationship">
<thead>
<tr>
<th>pets.Id</th>
<th>species.Id</th>
<th>pets.species</th>
<th>species.price</th>
</tr>
</thead>
<tbody>
<tr>
<th title="fido">fido</th>
<th title="dog">dog</th>
<td>dog</td>
<td>5</td>
</tr>
<tr>
<th title="cujo">cujo</th>
<th title="dog">dog</th>
<td>dog</td>
<td>5</td>
</tr>
</tbody>
</table>
`;
This example creates a Provider context into which a default Relationships object is provided. The RelationshipInHtmlTable component within it then renders the two Tables linked by a relationship with a custom component and a custom props callback for the species Cell. The header row at the top of the table and the Id column at the start of each row is removed.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createRelationships, createStore} from 'tinybase';
import {CellView, Provider} from 'tinybase/ui-react';
import {RelationshipInHtmlTable} from 'tinybase/ui-react-dom';
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = () => (
<RelationshipInHtmlTable
relationshipId="petSpecies"
customCells={customCells}
idColumn={false}
headerRow={false}
/>
);
const FormattedCellView = ({tableId, rowId, cellId, store, bold}) => (
<>
{bold ? <b>{rowId}</b> : rowId}:
<CellView
tableId={tableId}
rowId={rowId}
cellId={cellId}
store={store}
/>
</>
);
const customCells = {
'species.price': {
component: FormattedCellView,
getComponentProps: (rowId) => ({bold: rowId == 'dog'}),
},
};
const relationships = createRelationships(
createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'wolf'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// ->
`
<table>
<tbody>
<tr>
<td><b>dog</b>:5</td>
</tr>
<tr>
<td>wolf:10</td>
</tr>
</tbody>
</table>
`;
Since
v4.1.0
Store components
TableInHtmlTable
The TableInHtmlTable component renders the contents of a single Table in a Store as an HTML <table> element, and registers a listener so that any changes to that result will cause a re-render.
TableInHtmlTable(props: TableInHtmlTableProps & HtmlTableProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | TableInHtmlTableProps & HtmlTableProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the |
See the <TableInHtmlTable /> demo for this component in action.
The component's props identify which Table to render based on Table Id, and Store (which is either the default context Store, a named context Store, or by explicit reference).
This component renders a Table by iterating over its Row objects. By default the Cells are in turn rendered with the CellView component, but you can override this behavior by providing a component for each Cell in the customCells prop. You can pass additional props to that custom component with the getComponentProps callback. See the CustomCell type for more details.
This component uses the useRowIds hook under the covers, which means that any changes to the structure of the Table will cause a re-render.
You can use the headerRow and idColumn props to control whether the Ids appear in a <th> element at the top of the table, and the start of each row.
Examples
This example creates a Provider context into which a default Store is provided. The TableInHtmlTable component within it then renders the Table in a <table> element with a CSS class.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider} from 'tinybase/ui-react';
import {TableInHtmlTable} from 'tinybase/ui-react-dom';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <TableInHtmlTable tableId="pets" className="table" />;
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// ->
`
<table class="table">
<thead>
<tr>
<th>Id</th>
<th>species</th>
</tr>
</thead>
<tbody>
<tr>
<th title="fido">fido</th>
<td>dog</td>
</tr>
<tr>
<th title="felix">felix</th>
<td>cat</td>
</tr>
</tbody>
</table>
`;
This example creates a Provider context into which a default Store is provided. The TableInHtmlTable component within it then renders the Table with a custom component and a custom props callback for the species Cell. The header row at the top of the table and the Id column at the start of each row is removed.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {CellView, Provider} from 'tinybase/ui-react';
import {TableInHtmlTable} from 'tinybase/ui-react-dom';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<TableInHtmlTable
tableId="pets"
customCells={customCells}
headerRow={false}
idColumn={false}
/>
);
const FormattedCellView = ({tableId, rowId, cellId, bold}) => (
<>
{bold ? <b>{rowId}</b> : rowId}:
<CellView tableId={tableId} rowId={rowId} cellId={cellId} />
</>
);
const customCells = {
species: {
component: FormattedCellView,
getComponentProps: (rowId) => ({bold: rowId == 'fido'}),
},
};
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// ->
`
<table>
<tbody>
<tr>
<td><b>fido</b>:dog</td>
</tr>
<tr>
<td>felix:cat</td>
</tr>
</tbody>
</table>
`;
Since
v4.1.0
SortedTableInHtmlTable
The SortedTableInHtmlTable component renders the contents of a single sorted Table in a Store, as an HTML <table> element, and registers a listener so that any changes to that result will cause a re-render.
SortedTableInHtmlTable(props: SortedTableInHtmlTableProps & HtmlTableProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | SortedTableInHtmlTableProps & HtmlTableProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the |
See the <SortedTableInHtmlTable /> demo for this component in action.
The component's props identify which Table to render based on Table Id, and Store (which is either the default context Store, a named context Store, or by explicit reference). It also takes a Cell Id to sort by and a boolean to indicate that the sorting should be in descending order. The offset and limit props are used to paginate results, but default to 0 and undefined to return all available Row Ids if not specified.
This component renders a ResultTable by iterating over its Row objects, in the order dictated by the sort parameters. By default the Cells are in turn rendered with the CellView component, but you can override this behavior by providing a component for each Cell in the customCells prop. You can pass additional props to that custom component with the getComponentProps callback. See the CustomCell type for more details.
This component uses the useSortedRowIds hook under the covers, which means that any changes to the structure or sorting of the Table will cause a re-render.
You can use the headerRow and idColumn props to control whether the Ids appear in a <th> element at the top of the table, and the start of each row.
The sortOnClick prop makes the table's sorting interactive such that the user can click on a column heading to sort by that column. The style classes sorted and ascending (or descending) are added so that you can provide hints to the user how the sorting is being applied.
Provide a paginator component for the Table with the paginator prop. Set to true to use the default SortedTablePaginator, or provide your own component that accepts SortedTablePaginatorProps.
Finally, the onChange prop lets you listen to a user's changes to the Table's sorting or pagination.
Examples
This example creates a Provider context into which a default Store is provided. The SortedTableInHtmlTable component within it then renders the Table in a <table> element with a CSS class.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider} from 'tinybase/ui-react';
import {SortedTableInHtmlTable} from 'tinybase/ui-react-dom';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<SortedTableInHtmlTable
tableId="pets"
cellId="species"
className="table"
/>
);
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// ->
`
<table class="table">
<thead>
<tr>
<th>Id</th>
<th class="sorted ascending">↑ species</th>
</tr>
</thead>
<tbody>
<tr>
<th title="felix">felix</th>
<td>cat</td>
</tr>
<tr>
<th title="fido">fido</th>
<td>dog</td>
</tr>
</tbody>
</table>
`;
This example creates a Provider context into which a default Store is provided. The SortedTableInHtmlTable component within it then renders the Table with a custom component and a custom props callback for the species Cell. The header row at the top of the table and the Id column at the start of each row is removed.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {CellView, Provider} from 'tinybase/ui-react';
import {SortedTableInHtmlTable} from 'tinybase/ui-react-dom';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<SortedTableInHtmlTable
tableId="pets"
cellId="species"
customCells={customCells}
headerRow={false}
idColumn={false}
/>
);
const FormattedCellView = ({tableId, rowId, cellId, bold}) => (
<>
{bold ? <b>{rowId}</b> : rowId}:
<CellView tableId={tableId} rowId={rowId} cellId={cellId} />
</>
);
const customCells = {
species: {
component: FormattedCellView,
getComponentProps: (rowId) => ({bold: rowId == 'fido'}),
},
};
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// ->
`
<table>
<tbody>
<tr>
<td>felix:cat</td>
</tr>
<tr>
<td><b>fido</b>:dog</td>
</tr>
</tbody>
</table>
`;
Since
v4.1.0
SortedTablePaginator
The SortedTablePaginator component renders a paginator for a sorted table.
SortedTablePaginator(props: SortedTablePaginatorProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | SortedTablePaginatorProps | The props for this component. |
| returns | ComponentReturnType | The rendering of a paginator control with a label, and next and previous buttons, where appropriate. |
See the <SortedTableInHtmlTable /> demo for this component in action.
The component displays 'previous' and 'next' buttons for paging through the Table if there are more Row Ids than fit in each page. The component will also display a label that shows which Row Ids are being displayed.
The component's props identify initial pagination settings, and it will fire an event when the pagination changes.
Example
This example creates a Provider context into which a default Store is provided. The SortedTableInHtmlTable component within it then renders the Table in a <table> element with a SortedTablePaginator (the default).
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider} from 'tinybase/ui-react';
import {SortedTableInHtmlTable} from 'tinybase/ui-react-dom';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<SortedTableInHtmlTable
tableId="pets"
cellId="species"
limit={2}
paginator={true}
/>
);
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'wolf'},
lowly: {species: 'worm'},
polly: {species: 'parrot'},
},
});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// ->
`
<table>
<caption>
<button class="previous" disabled="">←</button>
<button class="next">→</button>
1 to 2 of 5 rows
</caption>
<thead>
<tr>
<th>Id</th>
<th class="sorted ascending">↑ species</th>
</tr>
</thead>
<tbody>
<tr>
<th title="felix">felix</th>
<td>cat</td>
</tr>
<tr>
<th title="fido">fido</th>
<td>dog</td>
</tr>
</tbody>
</table>
`;
Since
v4.1.0
ValuesInHtmlTable
The ValuesInHtmlTable component renders the keyed value contents of a Store as an HTML <table> element, and registers a listener so that any changes to that result will cause a re-render.
ValuesInHtmlTable(props: ValuesInHtmlTableProps & HtmlTableProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | ValuesInHtmlTableProps & HtmlTableProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the |
See the <ValuesInHtmlTable /> demo for this component in action.
The component's props identify which Row to render based on Table Id, Row Id, and Store (which is either the default context Store, a named context Store, or an explicit reference).
This component renders a Store by iterating over its Value objects. By default the Values are in turn rendered with the ValueView component, but you can override this behavior by providing a valueComponent prop, a custom component of your own that will render a Value based on ValueProps. You can also pass additional props to your custom component with the getValueComponentProps callback prop.
This component uses the useValueIds hook under the covers, which means that any changes to the structure of the Values in the Store will cause a re-render.
You can use the headerRow and idColumn props to control whether labels and Ids appear in a <th> element at the top of the table, and the start of each row.
Examples
This example creates a Provider context into which a default Store is provided. The ValuesInHtmlTable component within it then renders the Values in a <table> element with a CSS class.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider} from 'tinybase/ui-react';
import {ValuesInHtmlTable} from 'tinybase/ui-react-dom';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <ValuesInHtmlTable className="values" />;
const store = createStore().setValues({open: true, employees: 3});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// ->
`
<table class="values">
<thead>
<tr>
<th>Id</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<th title="open">open</th>
<td>true</td>
</tr>
<tr>
<th title="employees">employees</th>
<td>3</td>
</tr>
</tbody>
</table>
`;
This example creates a Provider context into which a default Store is provided. The ValuesInHtmlTable component within it then renders the Row with a custom Cell component and a custom props callback. The header row at the top of the table and the Id column at the start of each row is removed.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider, ValueView} from 'tinybase/ui-react';
import {ValuesInHtmlTable} from 'tinybase/ui-react-dom';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const getBoldProp = (valueId) => ({bold: valueId == 'open'});
const Pane = () => (
<ValuesInHtmlTable
valueComponent={FormattedValueView}
getValueComponentProps={getBoldProp}
headerRow={false}
idColumn={false}
/>
);
const FormattedValueView = ({valueId, bold}) => (
<>
{bold ? <b>{valueId}</b> : valueId}
{': '}
<ValueView valueId={valueId} />
</>
);
const store = createStore().setValues({open: true, employees: 3});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// ->
`
<table>
<tbody>
<tr><td><b>open</b>: true</td></tr>
<tr><td>employees: 3</td></tr>
</tbody>
</table>
`;
Since
v4.1.0
EditableCellView
The EditableCellView component renders the value of a single Cell in a way that can be edited in a web browser, and registers a listener so that any changes to that result will cause a re-render.
EditableCellView(props: CellProps & {
className?: string;
showType?: boolean;
}): ComponentReturnType| Type | Description | |
|---|---|---|
props | CellProps & { className?: string; showType?: boolean; } | The props for this component. |
| returns | ComponentReturnType | An editable rendering of the |
See the <EditableCellView /> demo for this component in action.
The component's props identify which Cell to render based on Table Id, Row Id, Cell Id, and Store (which is either the default context Store, a named context Store, or an explicit reference).
A Cell contains a string, number, or boolean, so the value is rendered in an appropriate <input> tag and a button lets the user change type, if possible.
Set the showType prop to false to remove the ability for the user to see or change the Cell type. They will also not be able to change the type if there is a TablesSchema applied to the Store.
This component uses the useCell hook under the covers, which means that any changes to the specified Cell outside of this component will cause a re-render.
You can provide a custom className prop which well be used on the root of the resulting element. If omitted the element's class will be editableCell. The debugIds prop has no effect on this component.
Example
This example creates a Provider context into which a default Store is provided. The EditableCellView component within it then renders an editable Cell.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider} from 'tinybase/ui-react';
import {EditableCellView} from 'tinybase/ui-react-dom';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<EditableCellView tableId="pets" rowId="fido" cellId="color" />
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// ->
`
<div class="editableCell">
<button title="string" class="string">string</button>
<input value="brown">
</div>
`;
Since
v4.1.0
EditableValueView
The EditableValueView component renders the value of a single Value in a way that can be edited in a web browser, and registers a listener so that any changes to that result will cause a re-render.
EditableValueView(props: ValueProps & {
className?: string;
showType?: boolean;
}): ComponentReturnType| Type | Description | |
|---|---|---|
props | ValueProps & { className?: string; showType?: boolean; } | The props for this component. |
| returns | ComponentReturnType | An editable rendering of the |
See the <EditableValueView /> demo for this component in action.
The component's props identify which Value to render based on Table Id, Row Id, Value Id, and Store (which is either the default context Store, a named context Store, or an explicit reference).
A Value contains a string, number, or boolean, so the value is rendered in an appropriate <input> tag and a button lets the user change type, if possible.
Set the showType prop to false to remove the ability for the user to see or change the Value type. They will also not be able to change the type if there is a ValuesSchema applied to the Store.
This component uses the useValue hook under the covers, which means that any changes to the specified Value outside of this component will cause a re-render.
You can provide a custom className prop which well be used on the root of the resulting element. If omitted the element's class will be editableValue. The debugIds prop has no effect on this component.
Example
This example creates a Provider context into which a default Store is provided. The EditableValueView component within it then renders an editable Value.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider} from 'tinybase/ui-react';
import {EditableValueView} from 'tinybase/ui-react-dom';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <EditableValueView valueId="employees" />;
const store = createStore().setValue('employees', 3);
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// ->
`
<div class="editableValue">
<button title="number" class="number">number</button>
<input type="number" value="3">
</div>
`;
Since
v4.1.0
Type Aliases
Configuration type aliases
ExtraRowCell
The ExtraRowCell object is used to configure the rendering of extra cells in an HTML table that's displaying a ResultTable or Table - before or after the regular data cells.
{
label: string;
component: ComponentType<RowProps>;
}| Type | Description | |
|---|---|---|
label | string | A string that will be used as the label at the top of the column for this |
component | ComponentType<RowProps> | A custom component for rendering each extra |
Since
v6.6.0
CustomCell
The CustomCell object is used to configure custom cell rendering in an HTML table.
{
label?: string;
component?: ComponentType<CellProps>;
getComponentProps?: (rowId: Id, cellId: Id) => ExtraProps;
}| Type | Description | |
|---|---|---|
label? | string | An optional string that will be used as the label at the top of the table column for this |
component? | ComponentType<CellProps> | An optional custom component for rendering each |
getComponentProps? | (rowId: Id, cellId: Id) => ExtraProps | An optional function for generating extra props for each custom |
Since
v4.1.0
CustomResultCell
The CustomResultCell object is used to configure custom cell rendering for query results in an HTML table.
{
label?: string;
component?: ComponentType<ResultCellProps>;
getComponentProps?: (rowId: Id, cellId: Id) => ExtraProps;
}| Type | Description | |
|---|---|---|
label? | string | An optional string that will be used as the label at the top of the table column for this |
component? | ComponentType<ResultCellProps> | An optional custom component for rendering each |
getComponentProps? | (rowId: Id, cellId: Id) => ExtraProps | An optional function for generating extra props for each custom |
Since
v4.1.0
ExtraValueCell
The ExtraValueCell object is used to configure the rendering of extra cells in an HTML table that's displaying Values - before or after the regular data cells.
{
label: string;
component: ComponentType<ValueProps>;
}| Type | Description | |
|---|---|---|
label | string | A string that will be used as the label at the top of the column for this |
component | ComponentType<ValueProps> | A custom component for rendering each extra |
Since
v6.6.0
Props type aliases
TableInHtmlTableProps
TableInHtmlTableProps props are used for components that will render a Table in an HTML table, such as the TableInHtmlTable component.
{
tableId: Id;
store?: StoreOrStoreId;
editable?: boolean;
customCells?: Ids | {[cellId: Id]: string | CustomCell};
extraCellsBefore?: ExtraRowCell[];
extraCellsAfter?: ExtraRowCell[];
}| Type | Description | |
|---|---|---|
tableId | Id | |
store? | StoreOrStoreId | The |
editable? | boolean | Whether the Cells should be editable. This affects the default |
customCells? | Ids | {[cellId: Id]: string | CustomCell} | An optional list of |
extraCellsBefore? | ExtraRowCell[] | An optional list of extra Cells to render before the main table Cells. |
extraCellsAfter? | ExtraRowCell[] | An optional list of extra Cells to render after the main table Cells. |
Since
v4.1.0
HtmlTableProps
HtmlTableProps props are used for components that will render in an HTML table, such as the TableInHtmlTable component or SortedTableInHtmlTable component.
{
className?: string;
headerRow?: boolean;
idColumn?: boolean;
}| Type | Description | |
|---|---|---|
className? | string | A string className to use on the root of the resulting element. |
headerRow? | boolean | Whether a header row should be rendered at the top of the table, defaulting to |
idColumn? | boolean | Whether an |
Since
v4.1.0
RelationshipInHtmlTableProps
RelationshipInHtmlTableProps props are used for components that will render the contents of the two Tables linked by a Relationship as an HTML table, such as the RelationshipInHtmlTable component.
{
relationshipId: Id;
relationships?: RelationshipsOrRelationshipsId;
editable?: boolean;
customCells?: Ids | {[cellId: Id]: string | CustomCell};
extraCellsBefore?: ExtraRowCell[];
extraCellsAfter?: ExtraRowCell[];
}| Type | Description | |
|---|---|---|
relationshipId | Id | The |
relationships? | RelationshipsOrRelationshipsId | The |
editable? | boolean | Whether the Cells should be editable. This affects the default |
customCells? | Ids | {[cellId: Id]: string | CustomCell} | An optional list of dotted 'tableId.cellId' string pairs to use for rendering a prescribed set of the relationship |
extraCellsBefore? | ExtraRowCell[] | An optional list of extra Cells to render before the main table Cells. |
extraCellsAfter? | ExtraRowCell[] | An optional list of extra Cells to render after the main table Cells. |
Note the use of dotted 'tableId.cellId' string pairs when specifying custom rendering for the cells in this table, since Cells from both the relationship's 'local' and 'remote' Table objects can be rendered and need to be distinguished.
Since
v4.1.0
ResultSortedTableInHtmlTableProps
ResultSortedTableInHtmlTableProps props are used for components that will render a sorted Table in an HTML table, such as the SortedTableInHtmlTable component.
{
queryId: Id;
cellId?: Id;
descending?: boolean;
offset?: number;
limit?: number;
queries?: QueriesOrQueriesId;
customCells?: Ids | {[cellId: Id]: string | CustomResultCell};
extraCellsBefore?: ExtraRowCell[];
extraCellsAfter?: ExtraRowCell[];
sortOnClick?: boolean;
paginator?: boolean | ComponentType<SortedTablePaginatorProps>;
onChange?: (sortAndOffset: [cellId: Id | undefined, descending: boolean, offset: number]) => void;
}| Type | Description | |
|---|---|---|
queryId | Id | The |
cellId? | Id | The |
descending? | boolean | Whether the sorting should be in descending order. |
offset? | number | |
limit? | number | |
queries? | QueriesOrQueriesId | The |
customCells? | Ids | {[cellId: Id]: string | CustomResultCell} | An optional list of |
extraCellsBefore? | ExtraRowCell[] | An optional list of extra Cells to render before the main table Cells. |
extraCellsAfter? | ExtraRowCell[] | An optional list of extra Cells to render after the main table Cells. |
sortOnClick? | boolean | Whether the table should be interactive such that clicking a header changes the sorting and/or direction. |
paginator? | boolean | ComponentType<SortedTablePaginatorProps> | Either |
onChange? | (sortAndOffset: [cellId: Id | undefined, descending: boolean, offset: number]) => void | A function that is called whenever the sorting or pagination of the |
Since
v4.1.0
ResultTableInHtmlTableProps
ResultTableInHtmlTableProps props are used for components that will render a ResultTable in an HTML table, such as the ResultTableInHtmlTable component.
{
queryId: Id;
queries?: QueriesOrQueriesId;
customCells?: Ids | {[cellId: Id]: string | CustomResultCell};
extraCellsBefore?: ExtraRowCell[];
extraCellsAfter?: ExtraRowCell[];
}| Type | Description | |
|---|---|---|
queryId | Id | The |
queries? | QueriesOrQueriesId | The |
customCells? | Ids | {[cellId: Id]: string | CustomResultCell} | An optional list of |
extraCellsBefore? | ExtraRowCell[] | An optional list of extra Cells to render before the main table Cells. |
extraCellsAfter? | ExtraRowCell[] | An optional list of extra Cells to render after the main table Cells. |
Since
v4.1.0
SliceInHtmlTableProps
SliceInHtmlTableProps props are used for components that will render an Index Slice in an HTML table, such as the SliceInHtmlTable component.
{
indexId: Id;
sliceId: Id;
indexes?: IndexesOrIndexesId;
editable?: boolean;
customCells?: Ids | {[cellId: Id]: string | CustomCell};
extraCellsBefore?: ExtraRowCell[];
extraCellsAfter?: ExtraRowCell[];
}| Type | Description | |
|---|---|---|
indexId | Id | |
sliceId | Id | |
indexes? | IndexesOrIndexesId | The |
editable? | boolean | Whether the Cells should be editable. This affects the default |
customCells? | Ids | {[cellId: Id]: string | CustomCell} | An optional list of |
extraCellsBefore? | ExtraRowCell[] | An optional list of extra Cells to render before the main table Cells. |
extraCellsAfter? | ExtraRowCell[] | An optional list of extra Cells to render after the main table Cells. |
Since
v4.1.0
SortedTableInHtmlTableProps
SortedTableInHtmlTableProps props are used for components that will render a sorted Table in an HTML table, such as the SortedTableInHtmlTable component.
{
tableId: Id;
cellId?: Id;
descending?: boolean;
offset?: number;
limit?: number;
store?: StoreOrStoreId;
editable?: boolean;
customCells?: Ids | {[cellId: Id]: string | CustomCell};
extraCellsBefore?: ExtraRowCell[];
extraCellsAfter?: ExtraRowCell[];
sortOnClick?: boolean;
paginator?: boolean | ComponentType<SortedTablePaginatorProps>;
onChange?: (sortAndOffset: [cellId: Id | undefined, descending: boolean, offset: number]) => void;
}| Type | Description | |
|---|---|---|
tableId | Id | |
cellId? | Id | The |
descending? | boolean | Whether the sorting should be in descending order. |
offset? | number | |
limit? | number | |
store? | StoreOrStoreId | The |
editable? | boolean | Whether the Cells should be editable. This affects the default |
customCells? | Ids | {[cellId: Id]: string | CustomCell} | An optional list of |
extraCellsBefore? | ExtraRowCell[] | An optional list of extra Cells to render before the main table Cells. |
extraCellsAfter? | ExtraRowCell[] | An optional list of extra Cells to render after the main table Cells. |
sortOnClick? | boolean | Whether the table should be interactive such that clicking a header changes the sorting and/or direction. |
paginator? | boolean | ComponentType<SortedTablePaginatorProps> | Either |
onChange? | (sortAndOffset: [cellId: Id | undefined, descending: boolean, offset: number]) => void | A function that is called whenever the sorting or pagination of the |
Since
v4.1.0
SortedTablePaginatorProps
SortedTablePaginatorProps props are used for components that will be used as a table paginator, such as the SortedTablePaginator component.
{
onChange: (offset: number) => void;
offset?: number;
limit?: number;
total: number;
singular?: string;
plural?: string;
}| Type | Description | |
|---|---|---|
onChange | (offset: number) => void | An event that will fire when the offset is updated, called with the new offset. |
offset? | number | |
limit? | number | |
total | number | |
singular? | string | A noun to use in the pagination label for a single row, defaulting to 'row'. |
plural? | string | A noun to use in the pagination label for multiple rows, defaulting to the value of the singular noun suffixed with the letter 's'. |
Since
v4.1.0
ValuesInHtmlTableProps
ValuesInHtmlTableProps props are used for components that will render Values in an HTML table, such as the ValuesInHtmlTable component.
{
store?: StoreOrStoreId;
editable?: boolean;
valueComponent?: ComponentType<ValueProps>;
getValueComponentProps?: (valueId: Id) => ExtraProps;
extraCellsBefore?: ExtraValueCell[];
extraCellsAfter?: ExtraValueCell[];
}| Type | Description | |
|---|---|---|
store? | StoreOrStoreId | The |
editable? | boolean | Whether the |
valueComponent? | ComponentType<ValueProps> | A custom component for rendering each |
getValueComponentProps? | (valueId: Id) => ExtraProps | A function for generating extra props for each custom |
extraCellsBefore? | ExtraValueCell[] | An optional list of extra Cells to render before the main table Cells. |
extraCellsAfter? | ExtraValueCell[] | An optional list of extra Cells to render after the main table Cells. |
Since
v4.1.0
ui-react-inspector
The ui-react-inspector module of the TinyBase project provides a component to help debug the state of your TinyBase stores and other objects.
The component in this module uses the react-dom module and so is not appropriate for environments like React Native.
See also
<Inspector /> demo
Since
v5.0.0
Functions
Inspector
The Inspector component renders a tool which allows you to view and edit the content of a Store in a debug web environment.
Inspector(props: InspectorProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | InspectorProps | The props for this component. |
| returns | ComponentReturnType | The rendering of the inspector tool. |
See the <Inspector /> demo for this component in action.
The component displays a nub in the corner of the screen which you may then click to interact with all the Store objects in the Provider component context.
The component's props identify the nub's initial location and panel state, though subsequent user changes to that will be preserved on each reload.
Example
This example creates a Provider context into which a default Store is provided. The Inspector component within it then renders the inspector tool.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {Provider} from 'tinybase/ui-react';
import {Inspector} from 'tinybase/ui-react-inspector';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <Inspector />;
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
// ...
console.log(app.innerHTML.substring(0, 30));
// -> '<aside id="tinybaseInspector">'
Since
v5.0.0
Type Aliases
InspectorProps
InspectorProps props are used to configure the Inspector component.
{
position?: "top" | "right" | "bottom" | "left" | "full";
open?: boolean;
hue?: number;
}| Type | Description | |
|---|---|---|
position? | "top" | "right" | "bottom" | "left" | "full" | An optional string to indicate where you want the inspector to first appear. |
open? | boolean | An optional boolean to indicate whether the inspector should start in the opened state. |
hue? | number | An optional number to indicate the hue of the inspector's UI, defaulting to 270. |
Since
v5.0.0